Skip to content

ggml: fix backend split scheduler race condition - #26040

Merged
0cc4m merged 2 commits into
masterfrom
0cc4m/backend-split-sync-fix
Aug 20, 2026
Merged

ggml: fix backend split scheduler race condition#26040
0cc4m merged 2 commits into
masterfrom
0cc4m/backend-split-sync-fix

Conversation

@0cc4m

@0cc4m 0cc4m commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Overview

Fixes #23321

Splits without input were running concurrently with other splits, while potentially reusing memory the other split is accessing. In this case, for Qwen models with -nkvo, one Vulkan split contains model.input_embed as input, while a following CPU split without inputs reuses the same memory. The Vulkan backend runs fully asynchronously, it just schedules the copies and the graph execution and returns, so the CPU backend was able to run immediately and overwrite the memory area used for the model.input_embed tensor, before the Vulkan backend actually read it.

The solution I chose here is to make sure all splits run sequentially, since currently the allocator assumes it can reuse memory in following splits. Potentially faster may be if the allocator took concurrency into account and didn't reuse memory for these cases, but that would be much more complicated.

I'm not that familiar with the ggml-backend.cpp code, so let me know if this is not the right way to handle it.

This did not affect CUDA because CUDA cpy_tensor_async does not run for CPU->GPU copies, it falls back to a synchronous copy.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, AI was used for debugging and prototyping the code, I manually reviewed and fixed.

splits without input were running concurrently with other splits, while potentially reusing memory the other split is accessing
@github-actions github-actions Bot added the ggml changes relating to the ggml tensor library for machine learning label Jul 23, 2026

@JohannesGaessler JohannesGaessler left a comment

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.

As of right now ggml backend events are used in unsafe ways. They are being typecast unconditionally so I think this patch will result in segfaults if multiple different backends (e.g. CUDA + Vulkan) are used together.

@0cc4m

0cc4m commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

I can't trigger any crash with this on my system with CUDA + Vulkan, but I don't know if there are edge cases. Are there plans to fix the way events are used? We could also just use synchronize here until then.

@ORippler

Copy link
Copy Markdown
Collaborator

@aendk for backend scheduling related matters

@ORippler

Copy link
Copy Markdown
Collaborator

There is a PR open to do alignment on a backend's expected beahvior: #25319 Unfortunately I have not had the time to follow-up on this

@aendk

aendk commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

For reference, I was able to reproduce this extremly rarely on underclocked NVIDIA GPUs.
It is harder to do so due to the synchronizations for each CPU-GPU copy. The race condition only occurs on the rare combo when 3 successive splits (CPU->CUDA->CPU) all have no inputs. In that case, the second CPU split can overwrite the output of the first split before it has been copied to CUDA; before the CUDA stream guarantees apply.

To fix this, several options spring to mind:

  1. force an additional sync if no syncs took place due to no input (this proposal)
  2. add another event mechanism so CPU split N+1 awaits successful copy of CPU split N
  3. separate output tensors; CPU split N and N+1 do not write to the same output tensor, no race can occur

As a hotfix, I think 1 is ok; I have not tested performance though.
In the longer term, moving away from synchronizations (like in #25319) is the way to go in my eyes.
Also, inside a single µ-batch, I think we should only synchronize what absolutely needs to be synchronized for maximum performance and that the scheduling is already complex as-is.
From this perspective, Option # 3 currently makes the most sense for me.

@0cc4m

0cc4m commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

I would like to get this fix merged in some way. @JohannesGaessler should it avoid using events entirely for now, or is it okay like this?

@ggerganov

Copy link
Copy Markdown
Member

As of right now ggml backend events are used in unsafe ways. They are being typecast unconditionally so I think this patch will result in segfaults if multiple different backends (e.g. CUDA + Vulkan) are used together.

I would like to get this fix merged in some way. @JohannesGaessler should it avoid using events entirely for now, or is it okay like this?

I don't see a problematic use case atm that would lead an event from one backend ending up handled by another backend. We can add asserts in the backends. Though this change should be good as is.

Comment thread ggml/src/ggml-backend.cpp
@ggerganov ggerganov added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Aug 18, 2026
@ggerganov
ggerganov requested a review from aendk August 18, 2026 06:37
@ORippler

Copy link
Copy Markdown
Collaborator

Shouldn't this one also be superseeded by #27311? Adding a ring-buffer will allow overlap/concurrency 🤔

@ggerganov

Copy link
Copy Markdown
Member

Shouldn't this one also be superseeded by #27311? Adding a ring-buffer will allow overlap/concurrency 🤔

It will take a while to review the #27311 and to me it's not obvious this it is the correct approach, so better to accept this for now.

@aendk

aendk commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Currently in the final stages of testing.

The question if this overlaps with #27311 is IMO moot. This is a good hotfix, and our medium-term plans move away from explicit syncs anyways (#25319) as outlined above in #26040 (comment)

@aendk

aendk commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Ok, the change looks good to me.
No measurable performance impact on UMA and dGPU systems, and the extra sync appears only where it is necessary.

@0cc4m
0cc4m merged commit 8497981 into master Aug 20, 2026
33 of 47 checks passed
@0cc4m
0cc4m deleted the 0cc4m/backend-split-sync-fix branch August 20, 2026 08:42
@ghost

ghost commented Aug 20, 2026

Copy link
Copy Markdown

No performance regression for ggml-hexagon, verified on Snapdragon 8 Gen 4 hardware.

Qualcomm's ggml-hexagon:
Screenshot from 2026-08-20 17-26-58

JZ's ggml-hexagon:
Screenshot from 2026-08-20 17-25-09

therealkenc pushed a commit to therealkenc/llama.cpp that referenced this pull request Aug 24, 2026
* ggml: fix backend split scheduler race condition

splits without input were running concurrently with other splits, while potentially reusing memory the other split is accessing

* only sync when split has no inputs
kmbandy added a commit to kmbandy/llama.cpp that referenced this pull request Aug 27, 2026
The spine sat idle for the whole worker round trip on all 48 layers. Three
things had to change together for the shared-expert FFN to actually run
during the RPC:

- ggml-backend scheduler: a split with no inputs used to force a full sync
  against the previous backend before starting, because gallocr may reuse
  buffer regions across splits (ggml-org#26040). The expert-dispatch WAIT is
  MAP_CUSTOM2 on CPU with only CPU sources, a different buffer type from the
  GPU shexp FFN, so that reuse cannot happen. Defer the sync for that one
  shape and drain the backend before the next split that really uses it,
  which HIP graph capture still needs.

- llm_graph_context::shexp_after_issue(): pin the shared expert's INPUT to
  the issue node instead of its output. Pinning the output made the GPU FFN
  a split that completed before the wait, so the deferred sync only
  overlapped a one-element acc with recv. The input is scaled by 1 first so
  acc_inplace cannot alias the activations that issue reads.

- complete_moe_dispatch(): build_wait() no longer takes shexp, and shexp is
  added first so graph expansion visits the GPU FFN split ahead of the CPU
  wait.

Also in this commit:

- A sidecar MTP/DFlash GGUF ships its own routed experts in-file but inherits
  the target's --expert-dispatch string and ctx_other, so it dispatched trunk
  layers to workers that do not hold them. Dispatch is now ignored unless the
  model itself sets weight_pager.routed_experts_external.

- wp-expert-worker: per-class arena placement fixes, and the worker library
  is now linked into llama-server.

- llama-context: LLM_ARCH_QWEN4EXP added to the large-graph node budget
  (rides along here; belongs with the upstream sync in the next commit).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjfM4FxGPEhoApv6yvWAzD
ravel7524 pushed a commit to ravel7524/llama.cpp that referenced this pull request Aug 30, 2026
* ggml: fix backend split scheduler race condition

splits without input were running concurrently with other splits, while potentially reusing memory the other split is accessing

* only sync when split has no inputs
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eval bug: Vulkan Backend no-kv-offload on Qwen3-Coder-Next and Qwen3.6-35B-A3B produces gibberish output

5 participants