diff --git a/ggml/src/ggml-backend-impl.h b/ggml/src/ggml-backend-impl.h index 40cea024c3d2..56f0090cce66 100644 --- a/ggml/src/ggml-backend-impl.h +++ b/ggml/src/ggml-backend-impl.h @@ -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); @@ -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 { diff --git a/ggml/src/ggml-backend.cpp b/ggml/src/ggml-backend.cpp index e519bdf50a1b..f1f7faad7fd5 100644 --- a/ggml/src/ggml-backend.cpp +++ b/ggml/src/ggml-backend.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #ifdef __APPLE__ @@ -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); } } @@ -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> 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> *) user_data; + std::vector & 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; @@ -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++) { @@ -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 & 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++) { diff --git a/ggml/src/ggml-cuda/common.cuh b/ggml/src/ggml-cuda/common.cuh index 14dd1098c97b..8c058ef7467a 100644 --- a/ggml/src/ggml-cuda/common.cuh +++ b/ggml/src/ggml-cuda/common.cuh @@ -1268,10 +1268,6 @@ struct ggml_cuda_concurrent_event { int n_streams = 0; std::unordered_map stream_mapping; - // Original order of nodes in this concurrent region (before interleaving) - // Used to restore grouping for fusion within streams - std::vector original_order; - const ggml_tensor * join_node; ggml_cuda_concurrent_event() = default; @@ -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; } @@ -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; @@ -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; diff --git a/ggml/src/ggml-cuda/ggml-cuda.cu b/ggml/src/ggml-cuda/ggml-cuda.cu index 2456f7dcc621..9f45b0a8b93a 100644 --- a/ggml/src/ggml-cuda/ggml-cuda.cu +++ b/ggml/src/ggml-cuda/ggml-cuda.cu @@ -4052,58 +4052,7 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud } } - if (should_launch_concurrent_events) { - // Restore original node order within each concurrent region to enable fusion within streams - - std::unordered_map node_to_idx; - node_to_idx.reserve(cgraph->n_nodes); - for (int i = 0; i < cgraph->n_nodes; ++i) { - node_to_idx[cgraph->nodes[i]] = i; - } - - for (auto & [fork_node, event] : stream_ctx.concurrent_events) { - // Find positions of all nodes from this event in the current graph - std::vector positions; - positions.reserve(event.original_order.size()); - - bool all_found = true; - for (const ggml_tensor * orig_node : event.original_order) { - auto it = node_to_idx.find(orig_node); - if (it != node_to_idx.end()) { - positions.push_back(it->second); - } else { - all_found = false; - break; - } - } - - if (!all_found || positions.size() != event.original_order.size()) { - continue; - } - - // Sort positions to get contiguous range - std::vector sorted_positions = positions; - std::sort(sorted_positions.begin(), sorted_positions.end()); - - bool is_contiguous = true; - for (size_t i = 1; i < sorted_positions.size(); ++i) { - if (sorted_positions[i] != sorted_positions[i-1] + 1) { - is_contiguous = false; - break; - } - } - - if (!is_contiguous) { - continue; - } - - // Restore original order at the sorted positions - int start_pos = sorted_positions[0]; - for (size_t i = 0; i < event.original_order.size(); ++i) { - cgraph->nodes[start_pos + i] = const_cast(event.original_order[i]); - } - } - } else { + if (!should_launch_concurrent_events) { stream_ctx.concurrent_events.clear(); } @@ -4328,7 +4277,7 @@ static void ggml_backend_cuda_event_wait(ggml_backend_t backend, ggml_backend_ev } } -static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph) { +static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph, ggml_backend_graph_optimize_params * params) { ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *) backend->context; #ifdef USE_CUDA_GRAPH @@ -4390,8 +4339,7 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph } for (int src_idx = 0; src_idx < GGML_MAX_SRC; ++src_idx) { const ggml_tensor * src = cgraph->nodes[node_idx]->src[src_idx]; - //TODO: check why nrows > 1 fails - if (node && !is_noop(node) && ggml_nrows(node) <= 1) { + if (node && !is_noop(node) && ggml_nrows(node) <= 8) { fan_out[src] += 1; } } @@ -4402,12 +4350,12 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph // 1. find fan-out (fork) nodes where the same input is used at least N times (in QKV, it would be "attn-norm") // 2. find the join node, where 2 or more of the outputs are required (in QKV, this would "KQ" or "flash-attn") // 3. account for all branches from the fork to the join - // 4. To extend lifetimes of the tensors, we interleave the branches (see below for more details) - // 5. save the original cgraph and restore it in graph_compute, to enable fusion within streams + // 4. add allocation dependencies to keep branch tensors alive until the join + // 5. save the stream mapping for graph_compute // See discussion: https://github.com/ggml-org/llama.cpp/pull/16991#issuecomment-3522620030 const int min_fan_out = 3; - const int max_fan_out = 3; + const int max_fan_out = 4; // store {fork_idx, join_idx} std::vector> concurrent_node_ranges; @@ -4444,7 +4392,7 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph GGML_ASSERT(nodes_per_branch.size() == (size_t) count); //find the join point - const ggml_tensor * join_node = nullptr; + ggml_tensor * join_node = nullptr; const auto & belongs_to_branch = [&](const ggml_tensor * node, const std::vector & branch) -> bool { @@ -4457,7 +4405,7 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph }; for (int i = root_node_idx + 1; i < cgraph->n_nodes; ++i) { - const ggml_tensor * curr_node = cgraph->nodes[i]; + ggml_tensor * curr_node = cgraph->nodes[i]; int num_joins = 0; for (size_t branch_idx = 0; branch_idx < nodes_per_branch.size(); branch_idx++) { @@ -4483,90 +4431,66 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph } } - if (!found_branch && is_noop(curr_node)) { - // we can put it in any branch because it will be ignored - nodes_per_branch[0].push_back({ curr_node }); + if (!found_branch) { + // Assign non-branch nodes to first branch's stream. + // Note: this is not correct but the most likely. is_valid() validates + // this assumption and returns false is this node would be used elsewhere. + nodes_per_branch[0].push_back(curr_node); } } if (join_node) { - //Create ggml_cuda_concurrent_event - ggml_cuda_concurrent_event concurrent_event(nodes_per_branch.size()); - concurrent_event.join_node = join_node; - - for (size_t branch_idx = 0; branch_idx < nodes_per_branch.size(); branch_idx++) { - for (const ggml_tensor * n : nodes_per_branch[branch_idx]) { - concurrent_event.stream_mapping[n] = branch_idx + 1; - } - } - int fork_node_idx = node_indices[root_node]; int join_node_idx = node_indices[join_node]; - int current_branch_idx = 0; - int current_node_idx = fork_node_idx + 1; - const int n_branches = nodes_per_branch.size(); + // remove branches if they start beyond join_node + nodes_per_branch.erase( + std::remove_if(nodes_per_branch.begin(), nodes_per_branch.end(), + [&](const std::vector & branch) { + const ggml_tensor * seed = branch.front(); + int seed_idx = node_indices[seed]; + return seed_idx < fork_node_idx + 1 || seed_idx >= join_node_idx; + }), + nodes_per_branch.end()); + + if (nodes_per_branch.size() < 2) { + continue; // not enough branches for concurrency + } + + //Create ggml_cuda_concurrent_event + ggml_cuda_concurrent_event concurrent_event(nodes_per_branch.size()); + concurrent_event.join_node = join_node; int total_branch_nodes = 0; - for (std::vector branch_nodes : nodes_per_branch) { + for (std::vector & branch_nodes : nodes_per_branch) { total_branch_nodes += branch_nodes.size(); } // there are other nodes in the middle which are unaccounted for // usually (cpy) nodes, then ignore this fork if (join_node_idx - fork_node_idx - 1 != total_branch_nodes) { - GGML_LOG_DEBUG( - "Skipping %s because the number of nodes in the middle is not equal to the total number of " - "branch nodes %d != %d\n", - root_node->name, join_node_idx - fork_node_idx - 1, total_branch_nodes); continue; } - // Save the original order of nodes in this region before interleaving - // This is used later to restore grouping for fusion within streams - concurrent_event.original_order.reserve(total_branch_nodes); - for (int i = fork_node_idx + 1; i < join_node_idx; ++i) { - concurrent_event.original_order.push_back(cgraph->nodes[i]); + for (size_t branch_idx = 0; branch_idx < nodes_per_branch.size(); branch_idx++) { + for (const ggml_tensor * n : nodes_per_branch[branch_idx]) { + concurrent_event.stream_mapping[n] = branch_idx + 1; + params->add_alloc_dep(params->user_data, const_cast(n), join_node); + for (int src_idx = 0; src_idx < GGML_MAX_SRC; ++src_idx) { + if (n->src[src_idx] != nullptr) { + params->add_alloc_dep(params->user_data, n->src[src_idx], join_node); + } + } + } } std::unordered_map & concurrent_events = cuda_ctx->stream_context().concurrent_events; GGML_ASSERT(concurrent_events.find(root_node) == concurrent_events.end()); + concurrent_events.emplace(root_node, std::move(concurrent_event)); - GGML_LOG_DEBUG("Adding stream at node %s %p\n", root_node->name, root_node); + GGML_LOG_DEBUG("GRAPH_OPT: created event for %s: %zu branches, %d total nodes\n", + root_node->name, nodes_per_branch.size(), total_branch_nodes); concurrent_node_ranges.emplace_back(fork_node_idx, join_node_idx); - - // interleave tensors to extend lifetimes so that ggml graph doesn't recycle them - // example transformation: - // [attn-norm, QMul, QNorm, QRope, KMul, KNorm, KRope, VMul, attn] -> - // [attn-norm, QMul, KMul, VMul, QNorm, VNorm, QRope, KRope, attn] - while (current_node_idx < join_node_idx) { - std::vector & branch_nodes = nodes_per_branch[current_branch_idx]; - - bool has_node = false; - for (std::vector branch_node : nodes_per_branch) { - has_node |= branch_node.size() > 0; - } - - GGML_ASSERT(has_node); - - if (branch_nodes.empty()) { - current_branch_idx = (current_branch_idx + 1) % n_branches; - continue; - } - - cgraph->nodes[current_node_idx] = const_cast(branch_nodes.front()); - current_node_idx++; - branch_nodes.erase(branch_nodes.begin()); - - // append all empty nodes - while (!branch_nodes.empty() && is_noop(branch_nodes.front())) { - cgraph->nodes[current_node_idx] = const_cast(branch_nodes.front()); - current_node_idx++; - branch_nodes.erase(branch_nodes.begin()); - } - - current_branch_idx = (current_branch_idx + 1) % n_branches; - } } } } diff --git a/ggml/src/ggml-hexagon/ggml-hexagon.cpp b/ggml/src/ggml-hexagon/ggml-hexagon.cpp index 53e860755910..e7dcdc3d5513 100644 --- a/ggml/src/ggml-hexagon/ggml-hexagon.cpp +++ b/ggml/src/ggml-hexagon/ggml-hexagon.cpp @@ -4984,7 +4984,9 @@ static std::vector ggml_hexagon_graph_optimize_reorder(const std::vectorn_nodes; constexpr int MAX_FUSE = 16; diff --git a/ggml/src/ggml-metal/ggml-metal.cpp b/ggml/src/ggml-metal/ggml-metal.cpp index 9756d47050c3..4d58dc821cf4 100644 --- a/ggml/src/ggml-metal/ggml-metal.cpp +++ b/ggml/src/ggml-metal/ggml-metal.cpp @@ -558,7 +558,9 @@ static void ggml_backend_metal_event_wait(ggml_backend_t backend, ggml_backend_e ggml_metal_event_wait(ctx, ev); } -static void ggml_backend_metal_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph) { +static void ggml_backend_metal_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph, ggml_backend_graph_optimize_params * params) { + GGML_UNUSED(params); + ggml_metal_t ctx = (ggml_metal_t)backend->context; ggml_metal_graph_optimize(ctx, cgraph); diff --git a/ggml/src/ggml-virtgpu/ggml-backend.cpp b/ggml/src/ggml-virtgpu/ggml-backend.cpp index 12756c9282f7..996c57e358b6 100644 --- a/ggml/src/ggml-virtgpu/ggml-backend.cpp +++ b/ggml/src/ggml-virtgpu/ggml-backend.cpp @@ -17,7 +17,8 @@ static ggml_status ggml_backend_remoting_graph_compute(ggml_backend_t backend, g return apir_backend_graph_compute(gpu, cgraph); } -static void ggml_backend_remoting_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph) { +static void ggml_backend_remoting_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph, ggml_backend_graph_optimize_params * params) { + UNUSED(params); virtgpu * gpu = DEV_TO_GPU(backend->device); #if true UNUSED(gpu); diff --git a/ggml/src/ggml-vulkan/ggml-vulkan.cpp b/ggml/src/ggml-vulkan/ggml-vulkan.cpp index 39b4cd359803..8fbb1359f406 100644 --- a/ggml/src/ggml-vulkan/ggml-vulkan.cpp +++ b/ggml/src/ggml-vulkan/ggml-vulkan.cpp @@ -17795,8 +17795,9 @@ static ggml_status ggml_backend_vk_graph_compute(ggml_backend_t backend, ggml_cg } // Sort the graph for improved parallelism. -static void ggml_vk_graph_optimize(ggml_backend_t backend, struct ggml_cgraph * graph) +static void ggml_vk_graph_optimize(ggml_backend_t backend, struct ggml_cgraph * graph, struct ggml_backend_graph_optimize_params * params) { + GGML_UNUSED(params); VK_LOG_DEBUG("ggml_vk_graph_optimize(" << graph->n_nodes << " nodes)"); ggml_backend_vk_context * ctx = (ggml_backend_vk_context *)backend->context; diff --git a/tests/test-alloc.cpp b/tests/test-alloc.cpp index 6d5428493e70..8f1a98aa03c3 100644 --- a/tests/test-alloc.cpp +++ b/tests/test-alloc.cpp @@ -19,6 +19,8 @@ struct dummy_backend_context { size_t alignment = 8; ggml_backend_buffer_i buffer_interface; + ggml_backend_device device; + ggml_backend backend; std::vector buffers; size_t allocated_total() const { @@ -83,7 +85,27 @@ static void dummy_backend_buffer_get_tensor(ggml_backend_buffer_t, const ggml_te static void dummy_backend_buffer_clear(ggml_backend_buffer_t, uint8_t) {} -// dummy_backend (not really a full backend, just provides what gallocr needs) +// ggml_backend_device interface + +static enum ggml_backend_dev_type dummy_backend_device_get_type(ggml_backend_dev_t) { + return GGML_BACKEND_DEVICE_TYPE_CPU; +} + +static bool dummy_backend_device_supports_op(ggml_backend_dev_t, const ggml_tensor *) { + return true; +} + +static bool dummy_backend_device_supports_buft(ggml_backend_dev_t device, ggml_backend_buffer_type_t buft) { + return device->context == buft->context; +} + +// ggml_backend interface + +static const char * dummy_backend_get_name(ggml_backend_t) { + return "dummy_backend"; +} + +// dummy_backend struct dummy_backend { std::unique_ptr context; @@ -104,6 +126,16 @@ static dummy_backend dummy_backend_init(size_t max_buffer_size, size_t alignment b.context->buffer_interface.get_tensor = dummy_backend_buffer_get_tensor; b.context->buffer_interface.clear = dummy_backend_buffer_clear; + b.context->device.context = b.context.get(); + b.context->device.iface.get_type = dummy_backend_device_get_type; + b.context->device.iface.supports_op = dummy_backend_device_supports_op; + b.context->device.iface.supports_buft = dummy_backend_device_supports_buft; + + b.context->backend.context = b.context.get(); + b.context->backend.device = &b.context->device; + b.context->backend.iface.get_name = dummy_backend_get_name; + + b.buffer_type.device = &b.context->device; b.buffer_type.context = b.context.get(); b.buffer_type.iface.get_name = dummy_backend_buffer_type_get_name; b.buffer_type.iface.alloc_buffer = dummy_backend_buffer_type_alloc_buffer; @@ -583,6 +615,41 @@ static void test_reallocation() { } } +static void test_backend_graph_optimize(ggml_backend_t, ggml_cgraph * graph, ggml_backend_graph_optimize_params * params) { + GGML_ASSERT(graph->n_nodes == 3); + params->add_alloc_dep(params->user_data, graph->nodes[0], graph->nodes[2]); +} + +static bool graph_reuses_allocation(bool add_alloc_dep) { + auto [ctx, graph, ctx_ptr] = make_context(); + + ggml_tensor * x[4]; + x[0] = make_input_with_size(ctx, 16); + x[1] = ggml_scale(ctx, x[0], 2.0f); + x[2] = ggml_scale(ctx, x[1], 2.0f); + x[3] = ggml_scale(ctx, x[2], 2.0f); + + ggml_set_output(x[3]); + ggml_build_forward_expand(graph, x[3]); + + dummy_backend backend = dummy_backend_init(SIZE_MAX); + if (add_alloc_dep) { + backend.context->backend.iface.graph_optimize = test_backend_graph_optimize; + } + + ggml_backend_t backend_ptr = &backend.context->backend; + ggml_backend_buffer_type_t buft = &backend.buffer_type; + ggml_backend_sched_ptr sched(ggml_backend_sched_new(&backend_ptr, &buft, 1, 8, false, true)); + GGML_ASSERT(ggml_backend_sched_alloc_graph(sched.get(), graph)); + + return x[1]->data == x[2]->data; +} + +static void test_graph_optimize_alloc_dep() { + GGML_ASSERT(graph_reuses_allocation(false)); + GGML_ASSERT(!graph_reuses_allocation(true)); +} + static void run(const char * name, void (*f)()) { printf("%s ", name); fflush(stdout); @@ -604,5 +671,6 @@ int main() { run("test_multiple_buffer_types", test_multiple_buffer_types); run("test_buffer_size_zero", test_buffer_size_zero); run("test_reallocation", test_reallocation); + run("test_graph_optimize_alloc_dep", test_graph_optimize_alloc_dep); return 0; }