Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
76870ad
ggml: add a scheduler sanitizer
am17an Jul 27, 2026
22994a6
ggml: add a non-fatal mode to the scheduler sanitizer
pwilkin Aug 17, 2026
0051b45
ggml,llama: let the sanitizer see direct writes to host tensors
pwilkin Aug 17, 2026
292887a
llama: give every graph input a name
pwilkin Aug 17, 2026
70192ac
cuda: verify the cgraph uid fast path instead of trusting it
pwilkin Aug 17, 2026
f141436
ggml-backend: ring buffer graph inputs when a backend computes on hos…
pwilkin Aug 17, 2026
9bdc6ea
ggml-backend,llama: rotate the graph input ring on the reuse path
pwilkin Aug 17, 2026
2dd7ff1
ggml-backend: document the graph input ring buffer in the scheduler docs
pwilkin Aug 17, 2026
c7f83de
ggml: report the allocation root in sanitizer race reports
pwilkin Aug 17, 2026
f543f23
ggml-backend: keep memory read in place by another backend out of the…
pwilkin Aug 17, 2026
9dea828
ggml-backend: run ops that alias their source on the source's backend
pwilkin Aug 17, 2026
20c6c5b
docs: document the backend scheduler
pwilkin Aug 18, 2026
5db5750
ggml-backend: do not ring buffer graph inputs for a CPU-only scheduler
pwilkin Aug 18, 2026
c8d05db
ggml-backend: only ring buffer graph inputs for an asynchronous backend
pwilkin Aug 18, 2026
508d9a2
ggml-backend: only move an aliasing op to a backend that supports it
pwilkin Aug 18, 2026
1486aa7
ggml: harden scheduler ring allocation
pwilkin Aug 20, 2026
b3823d8
cuda: simplify graph uid verification setting
pwilkin Aug 20, 2026
c530ea7
docs: clarify asynchronous output lifetime
pwilkin Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions docs/development/backend-scheduler.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ggml/include/ggml-alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ GGML_API bool ggml_gallocr_alloc_graph(ggml_gallocr_t galloc, struct ggml_cgraph

GGML_API size_t ggml_gallocr_get_buffer_size(ggml_gallocr_t galloc, int buffer_id);

GGML_API void ggml_gallocr_pin_tensor(ggml_gallocr_t galloc, struct ggml_tensor * t);
GGML_API void ggml_gallocr_clear_pins(ggml_gallocr_t galloc);

// Utils
// Create a buffer and allocate all the tensors in a ggml_context
// ggml_backend_alloc_ctx_tensors_from_buft_size returns the size of the buffer that would be allocated by ggml_backend_alloc_ctx_tensors_from_buft
Expand Down
44 changes: 4 additions & 40 deletions ggml/include/ggml-backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ extern "C" {
GGML_API void ggml_backend_tensor_get_2d(const struct ggml_tensor * tensor, void * data, size_t offset, size_t size, size_t n_copies, size_t stride_tensor, size_t stride_data);
GGML_API void ggml_backend_tensor_memset( struct ggml_tensor * tensor, uint8_t value, size_t offset, size_t size);

GGML_API void ggml_backend_tensor_set_direct(struct ggml_tensor * tensor, size_t offset, size_t size);

GGML_API void ggml_backend_synchronize(ggml_backend_t backend);

GGML_API ggml_backend_graph_plan_t ggml_backend_graph_plan_create(ggml_backend_t backend, struct ggml_cgraph * cgraph);
Expand Down Expand Up @@ -263,46 +265,7 @@ extern "C" {
// Backend scheduler
//

// The backend scheduler allows for multiple backend devices to be used together
// Handles compute buffer allocation, assignment of tensors to backends, and copying of tensors between backends
// The backends are selected based on:
// - the backend that supports the operation
// - the location of the pre-allocated tensors (e.g. the weights)
/*
Example usage:

// operations that use tensors allocated in a buffer with USAGE_WEIGHTS will be assigned
// preferably to run on the same backend as the buffer
ggml_backend_buffer_set_usage(buf_weights, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);

sched = ggml_backend_sched_new({backend_gpu, backend_gpu2, backend_cpu}, NULL, num_backends, GGML_DEFAULT_GRAPH_SIZE, false, true);

// initialize buffers from a max size graph (optional)
reserve_graph = build_graph(sched, max_batch_size);

// manually assign nodes to a backend (optional, should not be needed in most cases)
struct ggml_tensor * node = ggml_mul_mat(ctx, ...);
ggml_backend_sched_set_tensor_backend(sched, node, backend_gpu);

ggml_backend_sched_reserve(sched, reserve_graph);

// compute
graph = build_graph(sched); // the graph and its tensors are single-use in terms of allocation, multi-use in terms of computation
for (int i = 0; i < 10; ++i) {
ggml_backend_sched_graph_compute(sched, graph); // on the first iteration the graph is allocated automatically
}

// if there are graph inputs:
graph = build_graph(sched); // get a new graph that is not allocated (the metadata for the old graph is freed once ggml_free is called)
ggml_backend_sched_reset(sched); // clear the allocation of the previous graph
ggml_backend_sched_alloc_graph(sched, graph); // explicitly allocate the new graph but do not execute it
ggml_backend_tensor_set(input_tensor, ...); // copy data to the newly allocated graph tensors
ggml_backend_sched_graph_compute(sched, graph); // execute the graph

// as an alternative to the above it is also possible to assign the inputs to a dedicated context and
// allocate them statically via ggml_backend_alloc_ctx_tensors
}
*/
// see docs/development/backend-scheduler.md

typedef struct ggml_backend_sched * ggml_backend_sched_t;

Expand Down Expand Up @@ -341,6 +304,7 @@ extern "C" {

// Allocate and compute graph on the backend scheduler
GGML_API bool ggml_backend_sched_alloc_graph(ggml_backend_sched_t sched, struct ggml_cgraph * graph); // returns success
GGML_API void ggml_backend_sched_prepare_inputs(ggml_backend_sched_t sched);
GGML_API enum ggml_status ggml_backend_sched_graph_compute(ggml_backend_sched_t sched, struct ggml_cgraph * graph);
GGML_API enum ggml_status ggml_backend_sched_graph_compute_async(ggml_backend_sched_t sched, struct ggml_cgraph * graph);
GGML_API void ggml_backend_sched_synchronize(ggml_backend_sched_t sched);
Expand Down
2 changes: 2 additions & 0 deletions ggml/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ add_library(ggml-base
ggml-alloc.c
ggml-backend.cpp
ggml-backend-meta.cpp
ggml-backend-sanitize.cpp
ggml-backend-sanitize.h
ggml-opt.cpp
ggml-threading.cpp
ggml-threading.h
Expand Down
45 changes: 45 additions & 0 deletions ggml/src/ggml-alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,9 @@ struct ggml_gallocr {

struct leaf_alloc * leaf_allocs; // [n_leafs]
int n_leafs;

struct ggml_hash_set pinned;
bool has_pinned;
};

ggml_gallocr_t ggml_gallocr_new_n(ggml_backend_buffer_type_t * bufts, int n_bufs) {
Expand Down Expand Up @@ -569,6 +572,9 @@ void ggml_gallocr_free(ggml_gallocr_t galloc) {
}

ggml_hash_set_free(&galloc->hash_set);
if (galloc->pinned.size > 0) {
ggml_hash_set_free(&galloc->pinned);
}
free(galloc->hash_values);
free(galloc->bufts);
free(galloc->buffers);
Expand All @@ -589,6 +595,16 @@ static bool ggml_gallocr_is_own(ggml_gallocr_t galloc, struct ggml_tensor * t) {
return ggml_gallocr_hash_get(galloc, t)->allocated;
}

static bool ggml_gallocr_is_pinned(ggml_gallocr_t galloc, struct ggml_tensor * t) {
if (!galloc->has_pinned) {
return false;
}
while (t->view_src != NULL) {
t = t->view_src;
}
return ggml_hash_contains(&galloc->pinned, t);
}

static bool ggml_gallocr_is_allocated(ggml_gallocr_t galloc, struct ggml_tensor * t) {
return t->data != NULL // tensor data already set externally
|| t->buffer // tensor on external buffer (but not yet allocated)
Expand Down Expand Up @@ -641,6 +657,11 @@ static void ggml_gallocr_allocate_node(ggml_gallocr_t galloc, struct ggml_tensor
continue;
}

if (ggml_gallocr_is_pinned(galloc, parent)) {
AT_PRINTF("not reusing parent %s for %s as it is pinned\n", parent->name, node->name);
continue;
}

// outputs cannot be reused
if (parent->flags & GGML_TENSOR_FLAG_OUTPUT || (parent->view_src != NULL && parent->view_src->flags & GGML_TENSOR_FLAG_OUTPUT)) {
AT_PRINTF("not reusing parent %s for %s as it is an output\n", parent->name, node->name);
Expand Down Expand Up @@ -687,13 +708,37 @@ static void ggml_gallocr_allocate_node(ggml_gallocr_t galloc, struct ggml_tensor
}
}

void ggml_gallocr_pin_tensor(ggml_gallocr_t galloc, struct ggml_tensor * t) {
GGML_ASSERT(galloc);
if (galloc->pinned.size == 0) {
galloc->pinned = ggml_hash_set_new(GGML_DEFAULT_GRAPH_SIZE);
}
if (ggml_hash_insert(&galloc->pinned, t) == GGML_HASHSET_FULL) {
GGML_ABORT("%s: pinned tensor set is full\n", __func__);
}
galloc->has_pinned = true;
}

void ggml_gallocr_clear_pins(ggml_gallocr_t galloc) {
GGML_ASSERT(galloc);
if (galloc->pinned.size > 0) {
ggml_hash_set_reset(&galloc->pinned);
}
galloc->has_pinned = false;
}

static void ggml_gallocr_free_node(ggml_gallocr_t galloc, struct ggml_tensor * node) {
// graph outputs are never freed
if (node->flags & GGML_TENSOR_FLAG_OUTPUT) {
AT_PRINTF("not freeing output %s\n", node->name);
return;
}

if (ggml_gallocr_is_pinned(galloc, node)) {
AT_PRINTF("not freeing pinned %s\n", node->name);
return;
}

struct hash_node * hn = ggml_gallocr_hash_get(galloc, node);
int buffer_id = hn->buffer_id;
struct ggml_dyn_tallocr * alloc = galloc->buf_tallocs[buffer_id];
Expand Down
Loading
Loading