Skip to content

Formalizing scheduler and async backend behavior through tests - #27258

Open
aendk wants to merge 34 commits into
ggml-org:masterfrom
aendk:akieslinger/sched-backend-test-coverage-master
Open

aendk wants to merge 34 commits into
ggml-org:masterfrom
aendk:akieslinger/sched-backend-test-coverage-master

Conversation

@aendk

@aendk aendk commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Overview

This draft PR takes a first stab at formalizing the behavior of the scheduler and the async backends through test coverage.
There is lots to improve here, and I look forward to do so with your input. The main contribution is test-backend-sched.cpp. To create more complex test-cases, I also added a new operator OP_SLEEP (see below for details).

I've implemented several classes of tests for this:

  1. Test for formalizing behavior: Testing memcpys of user inputs and activations from and to every present backend, and also tests for specific patterns (e.g. Y-shaped graphs)
  2. Tests reproducing current bugs and confirming proposed fixes (ggml: fix backend split scheduler race condition #26040). The failing test-cases here (using vulkan) are a feature; the PR by @0cc4m lets all current tests (as of August 17th) succeed. Also, ggml: asynchronous scheduler memory copies #25319 fails, too (@JohannesGaessler).
  3. Stress tests which test scheduling of simple (linked-list) and more complex (directed acyclic graph) schedules.

New OP_SLEEP

  • to create a reliable bug repro for ggml: fix backend split scheduler race condition #26040, I needed a way to consistently delay device processing. I landed on OP_SLEEP as opposed to some big MatMul for the following reasons:
    • OP_SLEEP was easy to implement in this scope (CPU, CUDA, VK)
    • sleep is not machine specific, so correlation between devices in CI and on local developer machines (=better GPUs) is easier
    • reproduction of errors is easier as sleep can be in-/decreased very granularly
    • Will be run run millions of times in CI going forward, so reduces resource waste a bit.
  • If specific platforms do not support it, a proxy for it could still be a large enough MatMul / other expensive ops on the specific devices. Need input from the respective backend maintainers if this op can be implemented in their backend.

TODO

  • tests are currently very CPU/CUDA-centric, since these are the platforms I have easy access to, and to keep it simple. Will increase scope to vulkan and will integrate opportunistic testing. Whatever backend is present will be tested.
  • Test improvements (pseudo-random small sleeps inside the stress tests to trip up scheduling)
  • More tests for weird shapes (eg "X"-shaped) graphs

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, I leveraged AI to create much of this PR. Validity checking was done via nvtx-ranges and NSight Systems, the tests work as designed on CUDA, CPU and Vulkan. I steered the AI to keep changes to a minimum, but I need expert feedback on parts of the codebase I am unfamiliar with, such as the CPU and Vulkan backends (@0cc4m)

cc @ggerganov @am17an @ORippler @gaugarg-nv

@github-actions github-actions Bot added testing Everything test related Vulkan Issues specific to the Vulkan backend ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend labels Aug 17, 2026
@aendk
aendk force-pushed the akieslinger/sched-backend-test-coverage-master branch from fe9c5d9 to cd53112 Compare August 17, 2026 12:27
@ggerganov ggerganov self-assigned this Aug 18, 2026
@Anbeeld

Anbeeld commented Aug 26, 2026

Copy link
Copy Markdown

I opened a multi-GPU tensor-split issue that may be a useful real-world target for this scheduler/backend test work:

#27750

The failure is strongly timing-sensitive and so far I have not been able to reduce it to a deterministic backend test.

Setup is Windows + 2x RTX 3090 + Qwen 3.8 27B tensor split. I also reproduced a related failure on official upstream b10595 with MTP disabled.

A few things make it interesting for this PR:

  • slower synchronization/single-stream variants often make the model workload pass;
  • those variants reduce prefill from ~1349 tok/s to roughly 795-806 tok/s, so they appear to be timing perturbations rather than fixes;
  • an exact frozen 14,425-token fresh request passed three times even though similar-size runs had previously failed;
  • CUDA graphs off did not eliminate the problem;
  • graph reuse disabled did not eliminate it either;
  • failures happen with ample VRAM;
  • the exact projected MUL_MAT observed near one failure works correctly in isolation.

This is exactly the kind of bug where turning the timing relationship into a small deterministic scheduler/backend RED would be much more useful than continuing to reproduce it with 20k+ token model workloads.

I can run specific synthetic tests if there is a scheduler/meta pattern you want exercised.

@aendk

aendk commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@Anbeeld interesting, thanks for linking this.
I am currently working on expanding this PR to also cover multi-GPU scheduling more comprehensively.
Could be as early as sometime next week.

If you are curious and want to debug further in the mean time, I suggest disabling PDL (GGML_CUDA_PDL=0), CUDA graphs, and blocking execution(CUDA_LAUNCH_BLOCKING=1) to narrow it down further. Note however that all of this slows down CUDA execution, and the timing error might not appear due to this.

@aendk
aendk force-pushed the akieslinger/sched-backend-test-coverage-master branch 3 times, most recently from 3ef4094 to 7b48156 Compare September 21, 2026 08:21
@aendk
aendk marked this pull request as ready for review September 28, 2026 10:06
@aendk
aendk requested review from a team and ggerganov as code owners September 28, 2026 10:06
@aendk aendk changed the title [DRAFT]: Formalizing scheduler and async backend behavior through tests Formalizing scheduler and async backend behavior through tests Sep 30, 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.

So far I've taken a look at everything except the Vulkan changes and test-backend-sched.cpp. I'm not sure I agree with the specification/implementation of GGML_OP_SLEEP. It has I think more complexity than necessary and that is already causing slight inconsistencies between the CPU and CUDA backend. The CPU backend determines some point in the future, does the memcpy, and then waits until the predetermined time arrives. The CUDA backend sleeps for a set duration and then does a memcpy after the time has arrived. I can imagine scenarios where some ggml backend writes to the original buffer in the meantime and causes inconsistent results.

My opinion is that GGML_OP_SLEEP should only cause a delay without any memcpys. This can be done by creating a view of the original tensor (see ggml_view_impl in ggml.c). A backend will then only need to worry about implementing the delay without touching any actual data. This would also enable the use ggml_sleep for non-contiguous tensors. For purposes of ggml_backend_set and ggml_backend_get there should be no need to change test-sleep.cpp although an additional check asserting that ggml_tensor::data is the same could be added.

Comment thread ggml/src/ggml-cuda/ggml-cuda.cu Outdated
Comment thread ggml/src/ggml-cuda/sleep.cu Outdated
@JohannesGaessler

Copy link
Copy Markdown
Contributor

I've now read through test-backend-sched.cpp once. The things that are being tested all seem reasonable to me. I maybe have some comments regarding that some things could be implemented differently but I think we should avoid bikeshedding here so I will only comment on the important things. There are probably some relevant cases that are not yet covered and that I currently can't think of but anticipating these things ahead of time is hard; so I think the best way going forward will be to continually check new/changed functionality against the existing tests and to add more cases when something is not covered.

I don't feel confident in my Vulkan expertise so ideally those parts would be reviewed by one the corresponding maintainers.

@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.

Sorry, I tried to keep it short but it ended up being a lot anyways.

  • When I say in this review that things are "not being tested" I do not mean that adding such tests is a prerequisite for a merge. If you add such test cases I will review them but it's also fine to only add TODO comments for now.
  • Backend "promotion" like from CPU to GPU for better performance is not being tested.
  • The meta backend is not being tested.
  • Preferably use the RAII types like ggml_backend_ptr defined in ggml-cpp.h. It's very easy to forget freeing memory.
  • Preferably use a prefix like helper_ rather than sched_ to make it more clear that structures come from the tests rather than ggml. I don't feel strongly about this though.
  • I feel like in some parts of the code there is the intent to add "random" patterns like with reach_back in stress_test_dag. Preferable use an actual RNG instead to make the intent more clear.

Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
Comment on lines +181 to +190
// to later check if any further graph logic alters the number of splits
int n_splits_expected = g.nodes.empty() ? 0 : 1;
for (size_t i = 1; i < g.backend_id.size(); i++) {
n_splits_expected += g.backend_id[i] != g.backend_id[i - 1];
}

ggml_backend_sched_reset(sched);
for (size_t i = 0; i < g.nodes.size(); i++) {
ggml_backend_sched_set_tensor_backend(sched, g.nodes[i], backends_w_caps[g.backend_id[i]].backend);
}

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.

The way this is implemented sets an explicit backend for each tensor. However, one of the features of the ggml backend scheduler is that is automatically assigns backends where the user code does not do this. This feature is not covered by the tests.

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.

Correct, this is a required component to generate the specific schedules for the various scheduler-tests.

Have I understood you correctly that you would like me to add additional tests which primarily test that the auto-assign component of the scheduler works as intended?

Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
// GPU: increment both {99} and {66} -> {100} and {67}
// Correct result is thus {100}, incorrect output is {111} when {55} was overwritten by {66} before the copy started.
// Note: currently only reproducible on async H2D copy (=pinned memory).
static bool test_inputless_splits_scheduling(const sched_backend_caps & gpu, const sched_backend_caps & cpu, int64_t tensor_len, int32_t sleep_us, bool use_device_host_buft) {

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.

For this test to work it is implicitly relying on certain patterns of reuse in the ggml graph allocator. If the graph allocator for whatever reason were to decide that the memory should not be re-used it will not be overwritten even if a race condition does occur. So I would suggest making the overwrite explicit by using ggml_add_inplace rather than ggml_add.

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.

I think this would break the test. This one was made specifically for the case where the allocator thinks it can safely reuse the memory, but the scheduler does not schedule the write->read->write->read pattern correctly so that it collapsed into write->write->read->read.

I can of course add a flag here to also test withggml_add_inplace, but I think it would help to keep at least one version of this test as is, also because last time I tested, #25319 fails this test.

Comment thread tests/test-backend-sched.cpp Outdated
Comment thread tests/test-backend-sched.cpp Outdated
@aendk

aendk commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@0cc4m if you have time, please take a look into the the vulkan parts. They are 100% vibed and I am certain that you have quality opinions on how to improve them 👨‍🍳

@0cc4m 0cc4m 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.

I tested the Vulkan changes, looks good otherwise.

Comment thread ggml/src/ggml-vulkan/vulkan-shaders/feature-tests/shader_clock.comp Outdated
@aendk
aendk force-pushed the akieslinger/sched-backend-test-coverage-master branch 2 times, most recently from d013c05 to 9acd735 Compare October 15, 2026 09:40
aendk and others added 24 commits October 26, 2026 15:02
test_chain_all_backends and test_pair_user_inputs. The latter ones test
either activations or user inputs in isolation and just once, whereas
the stress test tests all combinations multiple rounds
Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
@aendk
aendk force-pushed the akieslinger/sched-backend-test-coverage-master branch from 7a2261a to 7428801 Compare October 26, 2026 14:02

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 testing Everything test related Vulkan Issues specific to the Vulkan backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants