Skip to content

ggml-cuda : Minimal pr 27311 version - #29450

Draft
mapatel-amd wants to merge 1 commit into
ggml-org:masterfrom
mapatel-amd:hip-integrated-sync-inputs
Draft

mapatel-amd wants to merge 1 commit into
ggml-org:masterfrom
mapatel-amd:hip-integrated-sync-inputs

Conversation

@mapatel-amd

Copy link
Copy Markdown

Assisted-by: Claude

Overview

The CPU and GPU in APUs share memory. When llama.cpp knows the device is an iGPU, it lets GPU read the model's inputs directly from the CPU instead of copying those inputs to the GPU's memory first. The problem with this is that the GPU's work is queued asynchronously, so when llama.cpp starts writing the next chunk's worth of inputs, it overwrites the previous inputs. This can happen while the GPU is in the process of reading those previous inputs, which produces very bad outputs, leading the PPL to be pretty high.

We suspected AMD's backend to be the issue because the HIP_LAUNCH_BLOCKING=1 flag makes the CPU wait after each GPU launch, forcing the CPU to wait and thereby preventing the overwrite. The HIP runtime, amdgpu driver and firmware weren't at fault, and this issue wasn't just showing up in APUs. It showed up in a gfx1036(iGPU)+gfx1100(dGPU) setup too.

The fix I have is that, first, we turn the integrated flag back on for HIP. Second, when llama.cpp sets up, check whether any GPU can read the CPU input buffer directly. Only in that case, wait for the previous GPU step to finish before writing new inputs. This concept already exists in llama.cpp as it is used in multi-GPU pipelines. This will also significantly speed up things compared to just setting the blocking flag.

Additional information

The goal of this PR is to be a minimal version of #27311.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: Yes, used it primarily to automate the multiple different build versions and testing.

@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 26, 2026
@mapatel-amd

mapatel-amd commented Sep 26, 2026 •

Copy link
Copy Markdown
Author

Here are some relevant tables with results from tests I performed using Claude. The markdown for the tables was made using Claude, but the table explanations themselves were written myself:

Hardware: Strix Halo gfx1151 (Radeon 8060S, 128 GB), ROCm 10.0, HIP backend. Base master 171e884.

  • master = unmodified
  • restore only = integrated = prop.integrated without the sync
  • this PR = restore + gated sync

Perplexity (wikitext-2, 4 chunks)

Model / config master restore only this PR
Qwen3-0.6B F16, -ngl 99 19.2844 34,296 19.2844
Qwen3-0.6B F16, -ot ffn_.*=CPU 19.2844 33,552 19.2844
Qwen3-30B-A3B Q4_K_M, -ngl 99 7.7015 210.41 7.7015
Qwen3-30B-A3B Q4_K_M, --n-cpu-moe 24 7.7015 552.67 7.7015
Qwen3.5-122B-A10B UD-Q4_K_XL, -ngl 99 3.3306 8817.95 3.3306
Qwen3.5-122B-A10B UD-Q4_K_XL, --n-cpu-moe 30 3.3306 8817.95 3.3306

This shows how well the model is predicting, lower is better. In this case, the score seen on master is the expected score and with only the .integrated re-enabled, the score is pretty bad. This is to be expected since multiple predictions are getting jumbled together. So, the sync helps in this case to make sure predictions don't get jumbled.

test-llama-archs -a llama -s 42 (20 runs each)

master restore only this PR
Passed 20/20 0/20 20/20

This is just llama.cpp's own tests, they are now all passing.

llama-server needle test (Qwen3-30B-A3B Q4_K_M, -np 4 -c 40960, 8 concurrent clients, 628-6752 token prompts, 120 requests)

master restore only this PR
Correct 119/120 97/120 119/120

Another test where we hide a secret code in a long document and ask the model to find it, 120 times, with 4 conversations running in parallel. Master was already failing on one of the tests, which is the model's own fault. We were able to get this back up to 119 with the sync.

Speed, experts on CPU (Qwen3-30B-A3B Q4_K_M, -ngl 99 -ncmoe 24 -fa 1, t/s)

Test master this PR Change
pp2048 1295.4 1638.7 +26.5%
pp2048 @ d16384 674.5 751.4 +11.4%
tg128 51.8 76.0 +46.7%
tg128 @ d16384 36.2 49.3 +36.2%

Testing the speed improvements with this PR when part of the model sits on the CPU. Fair bit of improvement imo.

Speed, full offload (Qwen3-30B-A3B Q4_K_M, -ngl 99 -fa 1, t/s)

Test master this PR
pp2048 1675.4 1663.1
pp2048 @ d32768 465.9 465.3
tg128 77.1 77.4
tg128 @ d32768 36.1 36.9

Testing the speed when the model is fully on the GPU. Master and this PR as essentially the same and the differences are due to noise.

Speed, long context (Qwen3.5-122B-A10B UD-Q4_K_XL, -ngl 99 -fa 1 -ctk q8_0 -ctv q8_0 -lm dio, t/s, mean of 2 runs)

Test master this PR Change
pp2048 @ d98304 171.8 188.7 +9.8%
tg128 @ d98304 11.7 12.7 +9.0%

Long context testing with about 98,000 tokens worth of context, about ~9-10% improvement.

Discrete GPU check (RX 7900 XTX gfx1100, Qwen3-0.6B F16, -ngl 99 -fa 1)

master this PR
Perplexity 19.3291 19.3291
pp2048 (t/s) 22542 22483
tg128 (t/s) 312.95 312.19

The sync that we do is only for iGPUs/APUs. So realistically it shouldn't make a difference for dGPUs and in this case, we can see that it doesn't.

test-backend-ops (gfx1151, ROCm0)

master this PR
16416/16416 16416/16416

Tested all the math operations in llama.cpp, they all passed.


Comparison with #27311

Same hardware, ROCm 10.0. Base 95ef7fc16 with integrated enabled.

  • unfixed = base
  • #27311 = PR head dced2ff78
  • this fix = same 28-line gated sync as this PR
  • integrated off = base with integrated forced off

Size

#27311 This PR
Lines added / removed +1,744 / -132 +32 / -0
Files changed 21 3

My changes are far more minimal. Though I did extensive testing on AMD hardware, I have NOT done any testing on NVIDIA hardware. I may need assistance with this.

Perplexity (wikitext-2, 4 chunks)

Model / config unfixed #27311 this fix integrated off
Qwen3-0.6B F16, -ngl 99 33k / 64k 19.2530 19.2530 19.2530
Qwen3-0.6B F16, -ngl 14 182.85 19.2805 19.2805 19.2530
Qwen3-0.6B F16, -ot ffn_.*=CPU 75k / 37k 19.2530 19.2530 19.2530
Qwen3-30B-A3B Q4_K_M, -ngl 99 244.12 7.6469 7.6469 7.6469
Qwen3-30B-A3B Q4_K_M, --n-cpu-moe 24 244.12 7.6469 7.6469 7.6469

#27311 gives identical results to my fix.

Speed, experts on CPU (Qwen3-30B-A3B Q4_K_M, -ngl 99 -ncmoe 24 -fa 1, t/s)

Test integrated off #27311 this fix
pp2048 1044.0 1264.1 1257.9
pp2048 @ d16384 568.6 615.1 617.3
tg128 49.7 72.6 71.6
tg128 @ d16384 35.5 47.8 47.3

The differences in speed when part of model is on CPU is very minimal, can be attributed to noise.

Speed, full offload (Qwen3-30B-A3B Q4_K_M, -ngl 99 -fa 1, t/s)

Test #27311 this fix
pp2048 1266.9 1266.2
pp2048 @ d32768 369.7 370.9
tg128 73.1 73.2
tg128 @ d32768 36.0 36.1

When the model is fully on the GPU, it's the same thing, very minimal noise which can be attributed to noise.

Speed, small model (Qwen3-0.6B F16, -ngl 99 -fa 1, t/s)

Test #27311 this fix
pp2048 9,821 9,736
pp2048 @ d32768 1,084 1,080
tg128 125.0 124.9
tg128 @ d32768 41.5 41.5

Even for smaller models, this pr's fix has minimal speed difference.

Race detector (#27311's GGML_SCHED_SANITIZE, ring disabled with GGML_SCHED_UMA_RING=1, Qwen3-0.6B F16, 2 chunks)

#27311, ring off #27311, ring off + this fix's sync
Races reported 5 0
Perplexity 12831.50 18.1384

These results show that my sync can possibly replace the ring buffer fixes.

#27311's other scheduler changes, with this fix's sync in place (#27311 head + this fix's sync, ring off, GGML_SCHED_SANITIZE=1, 4 chunks)

Model / config Races, pinning on Races, pinning off PPL pinning on PPL pinning off Alias moves
Qwen3-0.6B F16, -ngl 99 0 0 19.2530 19.2530 0
Qwen3-0.6B F16, -ngl 14 0 0 19.2805 19.2805 0
Qwen3-0.6B F16, -ot ffn_.*=CPU 0 0 19.2530 19.2530 0
Qwen3-0.6B F16, -nkvo 0 0 19.2530 19.2530 0
Qwen3-30B-A3B Q4_K_M, -ngl 99 0 0 7.6469 7.6469 0
Qwen3-30B-A3B Q4_K_M, --n-cpu-moe 24 0 0 7.6469 7.6469 0
Qwen3-30B-A3B Q4_K_M, -ot ffn_.*=CPU 0 0 7.6469 7.6469 0
Qwen3-30B-A3B Q4_K_M, -ngl 24 0 0 7.6710 7.6710 0
Qwen3.5-122B-A10B UD-Q4_K_XL, -ngl 99 0 0 3.3331 3.3331 0
Qwen3.5-122B-A10B UD-Q4_K_XL, --n-cpu-moe 30 0 0 3.3331 3.3331 0

Either way with pinning on and off, it made little to no difference. It turns out pinning only protects the same inputs this pr's sync already protects, so it's redundant.

Race detector under llama-server load (Qwen3-30B-A3B Q4_K_M, -np 4 -c 40960, 8 concurrent clients, 20 requests, ring off)

Build Pinning Races Needle correct
#27311 + this fix's sync off 0 20/20
#27311 + this fix's sync on 0 20/20
#27311, no sync off 105 13/20

Without my sync, we have far more races when doing the needle tests, thus resulting in more failing needle tests.

Comment thread src/llama-context.cpp

// the previous graph_compute_async may still be reading the inputs that are about to be overwritten
if (sync_host_inputs) {
ggml_backend_sched_synchronize(sched.get());

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.

This likely breaks pipeline parallel on many setups (performance reduction).

As stated in #27311 (comment) , supporting prop.integrated is low priority and there are very high barriers to get something like merged, due to the complexity of the scheduling code and the need to test many many setups. (dGPUs, UMA devices, various multi-GPU setups).

So before this moves forward, expect #27258 and #25319 to likely be first in line.

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

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.

2 participants