Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion ggml/src/ggml-backend-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ extern "C" {
// Backend (stream)
//

// passed to graph_optimize so the backend can add allocation dependencies:
// if the backend executes parts of the graph out of order (e.g. on concurrent streams),
// it must keep the affected tensors allocated until a node where execution is known to have joined
struct ggml_backend_graph_optimize_params {
// keep `tensor` allocated at least until `until` (a node of the same graph) has been computed
// can be called multiple times for the same tensor: the longest lifetime applies
void (*add_alloc_dep)(void * user_data, struct ggml_tensor * tensor, struct ggml_tensor * until);
void * user_data;
};

struct ggml_backend_i {
const char * (*get_name)(ggml_backend_t backend);

Expand Down Expand Up @@ -137,7 +147,7 @@ extern "C" {
void (*event_wait) (ggml_backend_t backend, ggml_backend_event_t event);

// (optional) sort/optimize the nodes in the graph
void (*graph_optimize) (ggml_backend_t backend, struct ggml_cgraph * cgraph);
void (*graph_optimize) (ggml_backend_t backend, struct ggml_cgraph * cgraph, struct ggml_backend_graph_optimize_params * params);
};

struct ggml_backend {
Expand Down
65 changes: 57 additions & 8 deletions ggml/src/ggml-backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <unordered_map>
#include <vector>

#ifdef __APPLE__
Expand Down Expand Up @@ -558,10 +559,10 @@ void ggml_backend_event_wait(ggml_backend_t backend, ggml_backend_event_t event)
backend->iface.event_wait(backend, event);
}

static void ggml_backend_graph_optimize(ggml_backend_t backend, struct ggml_cgraph * cgraph) {
static void ggml_backend_graph_optimize(ggml_backend_t backend, struct ggml_cgraph * cgraph, struct ggml_backend_graph_optimize_params * params) {
GGML_ASSERT(backend);
if (backend->iface.graph_optimize != NULL) {
backend->iface.graph_optimize(backend, cgraph);
backend->iface.graph_optimize(backend, cgraph, params);
}
}

Expand Down Expand Up @@ -1441,11 +1442,39 @@ void ggml_backend_sched_split_graph(ggml_backend_sched_t sched, struct ggml_cgra
sched->prev_leaf_backend_ids = tmp;
}

// optimize the split graphs and collect the allocation dependencies added by the backends
// this needs to happen before we make graph_copy, so they are in sync
std::unordered_map<ggml_tensor *, std::vector<ggml_tensor *>> alloc_deps;

struct ggml_backend_graph_optimize_params opt_params = {
/* .add_alloc_dep = */ [](void * user_data, ggml_tensor * tensor, ggml_tensor * until) {
auto & deps = *(std::unordered_map<ggml_tensor *, std::vector<ggml_tensor *>> *) user_data;
std::vector<ggml_tensor *> & keep = deps[until];
if (std::find(keep.begin(), keep.end(), tensor) == keep.end()) {
keep.push_back(tensor);
}
},
/* .user_data = */ &alloc_deps,
};

for (int i = 0; i < sched->n_splits; i++) {
struct ggml_backend_sched_split * split = &sched->splits[i];
split->graph = ggml_graph_view(graph, split->i_start, split->i_end);

ggml_backend_graph_optimize(sched->backends[split->backend_id], &split->graph, &opt_params);
}

// each dep is added to graph_copy as a GGML_OP_NONE node with the kept tensors as srcs
int n_dep_nodes = 0;
for (const auto & it : alloc_deps) {
n_dep_nodes += (it.second.size() + GGML_MAX_SRC - 1) / GGML_MAX_SRC;
}

int total_inputs = sched->n_graph_inputs;
for (int i = 0; i < sched->n_splits; i++) {
total_inputs += sched->splits[i].n_inputs;
}
int graph_size = std::max(graph->n_nodes, graph->n_leafs) + total_inputs * 2 * sched->n_copies;
int graph_size = std::max(graph->n_nodes, graph->n_leafs) + total_inputs * 2 * sched->n_copies + n_dep_nodes;

// remember the actual graph_size for performing reallocation checks later [GGML_SCHED_DEBUG_REALLOC]
sched->debug_prev_graph_size = sched->debug_graph_size;
Expand All @@ -1463,13 +1492,10 @@ void ggml_backend_sched_split_graph(ggml_backend_sched_t sched, struct ggml_cgra

struct ggml_cgraph * graph_copy = &sched->graph;

int n_dep_nodes_added = 0;

for (int i = 0; i < sched->n_splits; i++) {
struct ggml_backend_sched_split * split = &sched->splits[i];
split->graph = ggml_graph_view(graph, split->i_start, split->i_end);

// Optimize this split of the graph. This needs to happen before we make graph_copy,
// so they are in sync.
ggml_backend_graph_optimize(sched->backends[split->backend_id], &split->graph);

// add inputs to the graph copy so that they are allocated by ggml-alloc at the start of the split
for (int j = 0; j < split->n_inputs; j++) {
Expand All @@ -1494,9 +1520,32 @@ void ggml_backend_sched_split_graph(ggml_backend_sched_t sched, struct ggml_cgra
assert(graph_copy->size > graph_copy->n_nodes);
sched->node_backend_ids[graph_copy->n_nodes] = tensor_backend_id(graph->nodes[j]);
graph_copy->nodes[graph_copy->n_nodes++] = graph->nodes[j];

if (alloc_deps.empty()) {
continue;
}

// add a dependency node so that the kept tensors are not freed before this node is computed
auto it = alloc_deps.find(graph->nodes[j]);
if (it != alloc_deps.end()) {
const std::vector<ggml_tensor *> & keep = it->second;
for (size_t k = 0; k < keep.size(); k += GGML_MAX_SRC) {
struct ggml_tensor * dep = ggml_view_tensor(sched->ctx, keep[k]);
for (size_t s = 0; s < GGML_MAX_SRC && k + s < keep.size(); s++) {
dep->src[s] = keep[k + s];
}
assert(graph_copy->size > graph_copy->n_nodes);
sched->node_backend_ids[graph_copy->n_nodes] = split->backend_id;
graph_copy->nodes[graph_copy->n_nodes++] = dep;
n_dep_nodes_added++;
}
}
}
}

// a mismatch means a backend added a dep with an `until` tensor that is not a node of the optimized graph
GGML_ASSERT(n_dep_nodes_added == n_dep_nodes);

if (sched->n_copies > 1) {
// add input copies as leafs so that they are allocated first
for (int i = 0; i < sched->n_graph_inputs; i++) {
Expand Down
15 changes: 10 additions & 5 deletions ggml/src/ggml-cuda/common.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -1268,10 +1268,6 @@ struct ggml_cuda_concurrent_event {
int n_streams = 0;
std::unordered_map<const ggml_tensor *, int> stream_mapping;

// Original order of nodes in this concurrent region (before interleaving)
// Used to restore grouping for fusion within streams
std::vector<const ggml_tensor *> original_order;

const ggml_tensor * join_node;

ggml_cuda_concurrent_event() = default;
Expand All @@ -1294,7 +1290,6 @@ struct ggml_cuda_concurrent_event {
, fork_event(other.fork_event)
, n_streams(other.n_streams)
, stream_mapping(std::move(other.stream_mapping))
, original_order(std::move(other.original_order))
, join_node(other.join_node) {
other.fork_event = nullptr;
}
Expand All @@ -1317,6 +1312,11 @@ struct ggml_cuda_concurrent_event {
const int64_t t_start = (int64_t) t->data;
const int64_t t_end = t_start + ggml_nbytes(t);

// skip empty tensors
if (t_end == t_start) {
continue;
}

// skip tensors that overlap with join_node's buffer.
if ((t_start <= join_start && join_start < t_end) || (join_start <= t_start && t_start < join_end)) {
continue;
Expand All @@ -1338,6 +1338,11 @@ struct ggml_cuda_concurrent_event {
const int64_t t_start = (int64_t) t->data;
const int64_t t_end = t_start + ggml_nbytes(t);

// skip empty tensors
if (t_end == t_start) {
continue;
}

// skip tensors that overlap with join_node's buffer
if ((t_start <= join_start && join_start < t_end) || (join_start <= t_start && t_start < join_end)) {
continue;
Expand Down
Loading
Loading