#include "core/execution_engine.h" #include #include #include #include #include #include "core/ggml_extend_backend.h" #include "core/ggml_tensor_utils.h" #include "core/layer_split_partition.h" #include "core/segment_graph_bindings.h" #include "core/segment_weight_pipeline.h" #include "core/tensor_ggml.hpp" #include "core/util.h" std::shared_ptr DeviceResidencyManager::execution_engine() { if (execution_engine_ == nullptr) { execution_engine_ = std::make_shared(this); } return execution_engine_; } namespace sd { class GraphExecution { ExecutionEngine& engine_; ExecutionSession& session_; const ComputeTask& task_; std::shared_ptr manager_; std::unordered_set params_tensor_set_; ggml_status last_compute_status_ = GGML_STATUS_FAILED; Tensor output_; using GraphCutPlan = ggml_graph_cut::Plan; ggml_tensor* canonical_param_tensor(ggml_tensor* tensor); std::vector collect_used_param_tensors(ggml_cgraph* graph); ggml_backend_t backend_for_weight(const ggml_tensor* tensor) const; // Weightless ops follow the most recent weight device. Views must stay // unpinned so the scheduler preserves cross-device copies for consumers. void pin_multi_device_nodes(ggml_backend_sched_t sched, ggml_cgraph* graph, ggml_cgraph* original_graph = nullptr); Tensor read_graph_tensor(ggml_tensor* tensor, const char* label); void copy_data_to_backend_tensor(ggml_cgraph* graph); const GraphCutPlan& resolve_graph_cut_plan(ggml_cgraph* graph); bool assign_graph_cut_layer_split_backends(ggml_cgraph* graph); ComputeWorkspace::Measurement measure(ggml_cgraph* graph, const std::unordered_set* external_outputs = nullptr); std::vector memory_requests(const std::vector& sizes, size_t pending_cache_bytes) const; bool fits(const std::vector& requests, const std::vector& params) const; bool execute_segment(ggml_cgraph* graph, int n_threads, const std::function& on_before_compute); bool execute_graph(ggml_cgraph* graph, int n_threads, bool no_return, const std::function& read_outputs); bool is_multi_device() const { return !session_.extra_backends_.empty(); } size_t retained_runtime_buffer_bytes(ggml_backend_t backend) const { return session_.resident_bytes(backend); } void sync_runtime_residency() { engine_.sync_residency(session_); } public: GraphExecution(ExecutionEngine& engine, ExecutionSession& session, const ComputeTask& task) : engine_(engine), session_(session), task_(task), manager_(session.residency_manager_.lock()) { params_tensor_set_.insert(task.weights.begin(), task.weights.end()); if (manager_ != nullptr) { for (int i = 0; i < ggml_graph_cut::leaf_count(task.graph); ++i) { auto* parameter = manager_->resolve_param_tensor(ggml_graph_cut::leaf_tensor(task.graph, i)); if (parameter != nullptr) { params_tensor_set_.insert(parameter); } } for (auto* tensor : params_tensor_set_) { auto* parameter = manager_->resolve_param_tensor(const_cast(tensor)); if (parameter != nullptr) { session_.params_.insert(parameter); } } } } ComputeResult run() { if (!execute_graph(task_.graph, task_.n_threads, task_.no_return, task_.read_outputs)) { return {last_compute_status_, {}}; } return {GGML_STATUS_SUCCESS, std::move(output_)}; } }; static bool backend_can_allocate_op(ggml_backend_t backend, const ggml_tensor* node) { // Vulkan reports a preferred allocation chunk size, not a hard tensor limit. if (sd_backend_is(backend, "Vulkan")) { return true; } auto buft = ggml_backend_get_default_buffer_type(backend); size_t alignment = ggml_backend_buft_get_alignment(buft); size_t max_size = ggml_backend_buft_get_max_size(buft); max_size -= max_size % alignment; if (ggml_backend_buft_get_alloc_size(buft, node) > max_size) { return false; } // Cross-backend copies must also fit the destination's allocation limit. for (auto src : node->src) { if (src != nullptr && ggml_backend_buft_get_alloc_size(buft, src) > max_size) { return false; } } return true; } static size_t add_bytes(size_t a, size_t b) { return b > SIZE_MAX - a ? SIZE_MAX : a + b; } ggml_tensor* GraphExecution::canonical_param_tensor(ggml_tensor* tensor) { for (auto* current = tensor; current != nullptr; current = current->view_src) { if (params_tensor_set_.count(current) != 0) return current; } return nullptr; } std::vector GraphExecution::collect_used_param_tensors(ggml_cgraph* gf) { std::vector used_params; if (gf == nullptr || params_tensor_set_.empty()) { return used_params; } std::unordered_set seen_params; const int n_leafs = sd::ggml_graph_cut::leaf_count(gf); seen_params.reserve(static_cast(n_leafs)); for (int i = 0; i < n_leafs; ++i) { ggml_tensor* leaf = sd::ggml_graph_cut::leaf_tensor(gf, i); ggml_tensor* param_leaf = canonical_param_tensor(leaf); if (param_leaf != nullptr && seen_params.insert(param_leaf).second) { used_params.push_back(param_leaf); } } return used_params; } ggml_backend_t GraphExecution::backend_for_weight(const ggml_tensor* tensor) const { if (tensor == nullptr || tensor->buffer == nullptr) { return nullptr; } if (ggml_backend_buffer_get_usage(tensor->buffer) != GGML_BACKEND_BUFFER_USAGE_WEIGHTS || ggml_backend_buffer_is_host(tensor->buffer)) { return nullptr; } ggml_backend_dev_t dev = ggml_backend_buft_get_device(ggml_backend_buffer_get_type(tensor->buffer)); if (dev == nullptr) { return nullptr; } if (ggml_backend_get_device(session_.backend_) == dev) { return session_.backend_; } for (ggml_backend_t backend : session_.extra_backends_) { if (ggml_backend_get_device(backend) == dev) { return backend; } } return nullptr; } void GraphExecution::pin_multi_device_nodes(ggml_backend_sched_t sched, ggml_cgraph* gf, ggml_cgraph* original_graph) { if (sched == nullptr || gf == nullptr) { return; } ggml_backend_t current = session_.backend_; const int n_nodes = ggml_graph_n_nodes(gf); for (int i = 0; i < n_nodes; i++) { ggml_tensor* node = ggml_graph_node(gf, i); auto node_assignment = session_.node_assignments_.find(original_graph == nullptr ? node : ggml_graph_node(original_graph, i)); if (node_assignment != session_.node_assignments_.end()) { current = node_assignment->second; } for (int s = 0; s < GGML_MAX_SRC; s++) { ggml_backend_t weight_backend = backend_for_weight(node->src[s]); if (weight_backend != nullptr) { if (node_assignment == session_.node_assignments_.end()) { current = weight_backend; } } } if (node->op == GGML_OP_NONE || node->op == GGML_OP_VIEW || node->op == GGML_OP_RESHAPE || node->op == GGML_OP_PERMUTE || node->op == GGML_OP_TRANSPOSE) { continue; } if (!backend_can_allocate_op(current, node)) { auto cpu = session_.workspace_.cpu_backend(); if (cpu != nullptr && ggml_backend_supports_op(cpu, node)) { ggml_backend_sched_set_tensor_backend(sched, node, cpu); } } else if (ggml_backend_supports_op(current, node)) { ggml_backend_sched_set_tensor_backend(sched, node, current); } } } Tensor GraphExecution::read_graph_tensor(ggml_tensor* tensor, const char* label) { if (tensor == nullptr) { LOG_ERROR("%s %s tensor is null", task_.description.c_str(), label); return {}; } if (tensor->type != GGML_TYPE_F32) { LOG_ERROR("%s %s tensor type mismatch: got %s", task_.description.c_str(), label, ggml_type_name(tensor->type)); return {}; } ggml_backend_buffer_t buf = sd::ggml_graph_cut::tensor_buffer(tensor); if (buf == nullptr) { LOG_ERROR("%s %s tensor buffer missing: name=%s op=%s buffer=%p view_src=%p view_src_buffer=%p data=%p", task_.description.c_str(), label, tensor->name[0] != '\0' ? tensor->name : "", ggml_op_name(tensor->op), tensor->buffer, tensor->view_src, tensor->view_src ? tensor->view_src->buffer : nullptr, tensor->data); return {}; } return sd::make_sd_tensor_from_ggml(tensor); } void GraphExecution::copy_data_to_backend_tensor(ggml_cgraph* gf) { GGML_ASSERT(gf != nullptr); std::unordered_set graph_tensor_set; const int n_leafs = sd::ggml_graph_cut::leaf_count(gf); const int n_nodes = ggml_graph_n_nodes(gf); graph_tensor_set.reserve(static_cast(n_leafs + n_nodes)); for (int i = 0; i < n_leafs; ++i) { graph_tensor_set.insert(sd::ggml_graph_cut::leaf_tensor(gf, i)); } for (int i = 0; i < n_nodes; ++i) { graph_tensor_set.insert(ggml_graph_node(gf, i)); } for (auto& kv : task_.inputs) { auto tensor = kv.first; auto data = kv.second; if (tensor == nullptr || data == nullptr) { continue; } const char* name = ggml_get_name(tensor); if (graph_tensor_set.find(tensor) == graph_tensor_set.end()) { continue; } if (tensor->buffer == nullptr) { LOG_WARN("%s skip backend tensor copy: tensor buffer not set, name='%s', ne=[%lld,%lld,%lld,%lld], type=%s", task_.description.c_str(), name != nullptr ? name : "", (long long)tensor->ne[0], (long long)tensor->ne[1], (long long)tensor->ne[2], (long long)tensor->ne[3], ggml_type_name(tensor->type)); continue; } ggml_backend_buffer_t buf = tensor->view_src ? tensor->view_src->buffer : tensor->buffer; if (buf == nullptr) { LOG_WARN("%s graph exec skip tensor copy: name=%s op=%s reason=buffer_not_set data=%p view_src=%p view_src_buffer=%p", task_.description.c_str(), tensor && tensor->name[0] != '\0' ? tensor->name : "", tensor ? ggml_op_name(tensor->op) : "", data, tensor ? tensor->view_src : nullptr, (tensor && tensor->view_src) ? tensor->view_src->buffer : nullptr); continue; } ggml_backend_tensor_set(tensor, data, 0, ggml_nbytes(tensor)); } } const GraphExecution::GraphCutPlan& GraphExecution::resolve_graph_cut_plan(ggml_cgraph* gf) { GGML_ASSERT(gf != nullptr); return sd::ggml_graph_cut::resolve_plan(session_.backend_, gf, &session_.plan_cache_, params_tensor_set_, task_.description.c_str()); } bool GraphExecution::assign_graph_cut_layer_split_backends(ggml_cgraph* gf) { session_.node_assignments_.clear(); if (!session_.layer_split_enabled_) { return true; } if (!is_multi_device()) { LOG_ERROR("%s graph-cut layer split requires multiple runtime backends", task_.description.c_str()); return false; } const auto& plan = resolve_graph_cut_plan(gf); if (!plan.valid || !plan.has_cuts || plan.segments.size() <= 1) { auto manager = manager_; if (manager == nullptr) { LOG_ERROR("%s weight manager is not set for graph-cut layer split", task_.description.c_str()); return false; } std::vector graph_params = collect_used_param_tensors(gf); if (!graph_params.empty() && !manager->assign_compute_backend(graph_params, session_.backend_)) { LOG_ERROR("%s graph-cut layer split failed to assign unmarked graph params to %s", task_.description.c_str(), sd::layer_split_backend_device_display_name(session_.backend_).c_str()); return false; } for (ggml_tensor* param : graph_params) { if (param != nullptr) { session_.weight_assignments_[param] = session_.backend_; } } const int n_nodes = ggml_graph_n_nodes(gf); for (int i = 0; i < n_nodes; i++) { ggml_tensor* node = ggml_graph_node(gf, i); if (node != nullptr) { session_.node_assignments_[node] = session_.backend_; } } if (!session_.primary_notice_logged_) { LOG_WARN("%s graph-cut layer split: graph has no mark_graph_cut segments; using primary backend %s for %zu graph params", task_.description.c_str(), sd::layer_split_backend_device_display_name(session_.backend_).c_str(), graph_params.size()); session_.primary_notice_logged_ = true; } else { LOG_VERBOSE("%s graph-cut layer split: graph has no mark_graph_cut segments; using primary backend %s for %zu graph params", task_.description.c_str(), sd::layer_split_backend_device_display_name(session_.backend_).c_str(), graph_params.size()); } return true; } std::vector split_backends; split_backends.reserve(session_.extra_backends_.size() + 1); split_backends.push_back(session_.backend_); for (ggml_backend_t backend : session_.extra_backends_) { if (backend != nullptr) { split_backends.push_back(backend); } } auto manager = manager_; if (manager == nullptr) { LOG_ERROR("%s weight manager is not set for graph-cut layer split", task_.description.c_str()); return false; } sd::GraphCutLayerSplitAssignment assignment; auto canonicalize_param = [this](ggml_tensor* tensor) { return canonical_param_tensor(tensor); }; if (!sd::partition_graph_cut_layer_split(task_.description.c_str(), gf, plan, split_backends, session_.backend_limits_, session_.max_backend_bytes_, session_.weight_assignments_, canonicalize_param, &assignment)) { return false; } for (size_t i = 0; i < split_backends.size(); i++) { if (assignment.tensors_by_backend[i].empty()) { continue; } if (!manager->assign_compute_backend(assignment.tensors_by_backend[i], split_backends[i])) { LOG_ERROR("%s graph-cut layer split failed to assign params to %s", task_.description.c_str(), sd::layer_split_backend_device_display_name(split_backends[i]).c_str()); return false; } } session_.node_assignments_ = std::move(assignment.node_assignments); sd::log_graph_cut_layer_split_assignment(task_.description.c_str(), split_backends, assignment); return true; } ComputeWorkspace::Measurement GraphExecution::measure(ggml_cgraph* graph, const std::unordered_set* external_outputs) { auto external_backend = [&](const ggml_tensor* tensor) -> ggml_backend_t { if (external_outputs != nullptr && external_outputs->count(tensor)) { return session_.backend_; } if (!params_tensor_set_.count(tensor)) { return nullptr; } auto placement = session_.weight_assignments_.find(tensor); return placement == session_.weight_assignments_.end() ? session_.backend_ : placement->second; }; auto assign_nodes = [&](ggml_backend_sched_t scheduler, ggml_cgraph* copy) { pin_multi_device_nodes(scheduler, copy, graph); }; return session_.workspace_.measure(graph, external_backend, assign_nodes, !is_multi_device()); } std::vector GraphExecution::memory_requests( const std::vector& sizes, size_t pending_cache_bytes) const { std::vector requests; for (const auto& size : sizes) { const size_t retained = retained_runtime_buffer_bytes(size.backend); const size_t reusable = session_.workspace_.bytes(size.backend); const size_t cache_bytes = size.backend == session_.backend_ ? pending_cache_bytes : 0; const size_t pending = add_bytes(size.bytes > reusable ? size.bytes - reusable : 0, cache_bytes); size_t limit = session_.max_backend_bytes_; if (is_multi_device()) { size_t index = 0; if (size.backend != session_.backend_) { auto position = std::find(session_.extra_backends_.begin(), session_.extra_backends_.end(), size.backend); index = static_cast(position - session_.extra_backends_.begin()) + 1; } if (index < session_.backend_limits_.size()) { limit = session_.backend_limits_[index]; } } requests.push_back({size.backend, session_.owner_id(), pending, retained, limit}); } return requests; } bool GraphExecution::fits(const std::vector& requests, const std::vector& params) const { auto manager = manager_; if (manager == nullptr) { return params.empty(); } for (const auto& request : requests) { if (!manager->fits_compute_backend_capacity(request, params)) { return false; } } return true; } bool GraphExecution::execute_segment(ggml_cgraph* graph, int n_threads, const std::function& on_before_compute) { if (sd_backend_is_cpu(session_.backend_)) { sd_backend_cpu_set_n_threads(session_.backend_, n_threads); } if (session_.workspace_.cpu_backend() != nullptr) { sd_backend_cpu_set_n_threads(session_.workspace_.cpu_backend(), n_threads); } auto scheduler = session_.workspace_.scheduler(); GGML_ASSERT(scheduler != nullptr); ggml_backend_sched_set_eval_callback(scheduler, sd_get_backend_eval_callback(), sd_get_backend_eval_callback_data()); #ifndef SD_USE_UPSTREAM_GGML struct BeforeComputeCallback { ggml_backend_t backend; const std::function& callback; bool invoked = false; } before_compute{session_.backend_, on_before_compute}; ggml_backend_sched_set_compute_callback( scheduler, [](ggml_backend_t backend, void* data) { auto& hook = *static_cast(data); if (backend == hook.backend && !hook.invoked) { hook.invoked = true; hook.callback(); } }, &before_compute); const auto status = ggml_backend_sched_graph_compute_async(scheduler, graph); ggml_backend_sched_set_compute_callback(scheduler, nullptr, nullptr); #else on_before_compute(); const auto status = ggml_backend_sched_graph_compute_async(scheduler, graph); #endif session_.workspace_.synchronize(); if (status != GGML_STATUS_SUCCESS) { last_compute_status_ = status; LOG_ERROR("%s compute failed: %s", task_.description.c_str(), ggml_status_to_string(status)); return false; } const std::string description = task_.description; if (!task_.debug_tensors.empty()) { std::unordered_set graph_tensors; const int leaf_count = ggml_graph_cut::leaf_count(graph); const int node_count = ggml_graph_n_nodes(graph); graph_tensors.reserve(static_cast(leaf_count + node_count)); for (int index = 0; index < leaf_count; ++index) { graph_tensors.insert(ggml_graph_cut::leaf_tensor(graph, index)); } for (int index = 0; index < node_count; ++index) { graph_tensors.insert(ggml_graph_node(graph, index)); } for (const auto& entry : task_.debug_tensors) { ggml_tensor* tensor = entry.first; if (tensor == nullptr || graph_tensors.find(tensor) == graph_tensors.end()) { continue; } ggml_backend_buffer_t buffer = tensor->view_src != nullptr ? tensor->view_src->buffer : tensor->buffer; if (buffer == nullptr) { LOG_WARN("%s skip debug tensor '%s': tensor buffer not set", description.c_str(), entry.second.c_str()); continue; } if (tensor->type != GGML_TYPE_F32) { LOG_WARN("%s skip debug tensor '%s': only GGML_TYPE_F32 is supported, got %s", description.c_str(), entry.second.c_str(), ggml_type_name(tensor->type)); continue; } auto debug_tensor = make_sd_tensor_from_ggml(tensor); print_sd_tensor(debug_tensor, false, entry.second.c_str()); } } return true; } bool GraphExecution::execute_graph(ggml_cgraph* graph, int n_threads, bool no_return, const std::function& read_outputs) { if (!assign_graph_cut_layer_split_backends(graph)) { return false; } const auto params = collect_used_param_tensors(graph); const auto& cached_plan = resolve_graph_cut_plan(graph); auto manager = manager_; const bool can_segment = !is_multi_device() && !sd_backend_is_cpu(session_.backend_) && manager != nullptr && manager->segmented_compute_enabled() && cached_plan.valid && cached_plan.has_cuts && cached_plan.segments.size() > 1; // An empty workspace request is a lower bound on the full graph's memory demand. bool segmented = can_segment && !fits(memory_requests({{session_.backend_, 0}}, 0), params); ComputeWorkspace::Measurement full_measurement; if (!segmented) { full_measurement = measure(graph); if (full_measurement.buffers.empty()) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; return false; } if (can_segment) { // Planning headroom absorbs allocation estimate drift; execution keeps the normal limits. constexpr size_t planning_headroom = 128ULL * 1024ULL * 1024ULL; auto requests = memory_requests(full_measurement.buffers, session_.cache_.pending_bytes(graph)); for (auto& request : requests) { request.pending_allocation_bytes = add_bytes(request.pending_allocation_bytes, planning_headroom); } segmented = !fits(requests, params); } } ggml_graph_cut::Plan monolithic_plan; if (!segmented) { monolithic_plan.segments.emplace_back(); auto& segment = monolithic_plan.segments.back(); segment.group_name = "graph"; segment.compute_buffer_size = cached_plan.compute_buffer_size; segment.internal_node_indices.reserve(ggml_graph_n_nodes(graph)); segment.input_refs.reserve(ggml_graph_cut::leaf_count(graph)); for (int i = 0; i < ggml_graph_n_nodes(graph); ++i) { segment.internal_node_indices.push_back(i); } for (int i = 0; i < ggml_graph_cut::leaf_count(graph); ++i) { auto tensor = ggml_graph_cut::leaf_tensor(graph, i); ggml_graph_cut::Segment::InputRef input; input.leaf_index = i; input.type = canonical_param_tensor(tensor) != nullptr ? ggml_graph_cut::Segment::INPUT_PARAM : ggml_graph_cut::Segment::INPUT_EXTERNAL; segment.input_refs.push_back(input); } } const auto& plan = segmented ? cached_plan : monolithic_plan; const bool segments_changed = plan.segments.size() != session_.logged_segment_count_; if (segments_changed && (segmented || session_.logged_segment_count_ > 1)) { LOG_VERBOSE("%s using %zu segment%s", task_.description.c_str(), plan.segments.size(), plan.segments.size() == 1 ? "" : "s"); } SegmentGraphBindings bindings(session_.cut_cache_, plan, graph); SegmentWeightPipeline weights(manager, session_.backend_, session_.owner_id(), graph, plan, params_tensor_set_, segmented && manager != nullptr && manager->prefetch_enabled()); std::map peak_compute_bytes; auto track_compute_buffer = [&](ggml_backend_t backend) { if (backend != nullptr) { auto& peak = peak_compute_bytes[backend]; peak = std::max(peak, session_.workspace_.bytes(backend)); } }; for (size_t index = 0; index < plan.segments.size(); ++index) { const auto& segment = plan.segments[index]; const bool last = index + 1 == plan.segments.size(); auto fail_segment = [&](const char* phase) { LOG_ERROR("%s segment %zu/%zu (%s) failed during %s", task_.description.c_str(), index + 1, plan.segments.size(), segment.group_name.c_str(), phase); return false; }; session_.cut_cache_.prune(segment.live_cut_names); bindings.reset(segment); if (!bindings.bind_cached_inputs(segment, task_.description.c_str())) { return fail_segment("input binding"); } ggml_context* segment_context = nullptr; auto segment_graph = segmented ? ggml_graph_cut::build_segment_graph(graph, segment, &segment_context) : graph; struct SegmentCleanup { GraphExecution& runner; SegmentWeightPipeline& weights; SegmentGraphBindings& bindings; ggml_context* context; ~SegmentCleanup() { if (!runner.session_.workspace_.segment_end()) { runner.last_compute_status_ = GGML_STATUS_FAILED; } bindings.restore(); weights.segment_end(); ggml_free(context); runner.sync_runtime_residency(); } } segment_cleanup{*this, weights, bindings, segment_context}; std::unordered_set cache_outputs; if (segmented) { for (int output_index : segment.output_node_indices) { auto tensor = ggml_graph_node(graph, output_index); if (ggml_graph_cut::is_graph_cut_tensor(tensor) && segment.future_cut_names.count(tensor->name) && GraphCutTensorCache::can_bind_output(session_.backend_, tensor)) { cache_outputs.insert(tensor); } } } auto measurement = segmented ? measure(segment_graph, &cache_outputs) : full_measurement; if (!session_.workspace_.prepare(measurement)) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; return fail_segment("workspace preparation"); } const size_t cut_bytes = last ? 0 : session_.cut_cache_.estimate_output_bytes(graph, segment); size_t new_cache_bytes = add_bytes(cut_bytes, session_.cache_.pending_bytes(segment_graph)); auto ensure_capacity = [&]() { sync_runtime_residency(); auto requests = memory_requests(measurement.buffers, new_cache_bytes); if (fits(requests, weights.params(index))) { return true; } if (session_.workspace_.release_excess(measurement)) { sync_runtime_residency(); requests = memory_requests(measurement.buffers, new_cache_bytes); } const bool ready = weights.ensure_segment_capacity(index, requests); if (!ready && manager != nullptr) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; } return ready; }; if (!weights.segment_start(index, ensure_capacity)) { return fail_segment("weight preparation"); } // Preparing weights can execute LoRA graphs and reclaim an idle workspace. if (!session_.workspace_.prepare(measurement)) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; return fail_segment("workspace preparation"); } if (!ensure_capacity()) { return fail_segment("workspace capacity check"); } if (!session_.cut_cache_.bind_outputs(cache_outputs)) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; return fail_segment("output cache allocation"); } new_cache_bytes = add_bytes(last ? 0 : session_.cut_cache_.estimate_output_bytes(graph, segment), session_.cache_.pending_bytes(segment_graph)); if (!session_.workspace_.allocate(segment_graph, [&](ggml_backend_sched_t scheduler, ggml_cgraph* current) { pin_multi_device_nodes(scheduler, current); })) { last_compute_status_ = GGML_STATUS_ALLOC_FAILED; return fail_segment("workspace allocation"); } for (const auto& size : measurement.buffers) { track_compute_buffer(size.backend); } if (session_.workspace_.scheduler() != nullptr) { track_compute_buffer(session_.workspace_.cpu_backend()); } if (!ensure_capacity()) { return fail_segment("allocated capacity check"); } copy_data_to_backend_tensor(segment_graph); auto prefetch_requests = memory_requests(measurement.buffers, new_cache_bytes); if (!prefetch_requests.empty()) { weights.enqueue_next(index, prefetch_requests.front()); } LOG_DEBUG("%s executing segment %zu/%zu: %s", task_.description.c_str(), index + 1, plan.segments.size(), segment.group_name.c_str()); if (!execute_segment(segment_graph, n_threads, [&]() { weights.submit_next(); })) { return fail_segment("execution"); } auto cache_status = session_.cache_.capture(segment_graph); if (cache_status == GGML_STATUS_SUCCESS) { cache_status = session_.cut_cache_.capture(graph, segment, task_.description.c_str()); } if (cache_status != GGML_STATUS_SUCCESS) { last_compute_status_ = cache_status; return fail_segment("output caching"); } sync_runtime_residency(); if (last) { if (read_outputs && !read_outputs()) { return fail_segment("output finalization"); } if (!no_return) { output_ = read_graph_tensor(task_.output, "output"); if (output_.empty()) { return fail_segment("output readback"); } } } if (!session_.workspace_.segment_end()) { last_compute_status_ = GGML_STATUS_FAILED; return fail_segment("workspace synchronization"); } // Final outputs and their callbacks may still be views of consumed cuts. session_.cut_cache_.prune(segment.future_cut_names); } if (segments_changed || peak_compute_bytes != session_.logged_compute_bytes_) { for (const auto& entry : peak_compute_bytes) { LOG_VERBOSE("%s compute buffer size: %.2f MB(%s) on %s (peak across %zu segment%s)", task_.description.c_str(), entry.second / (1024.0 * 1024.0), sd_backend_is_cpu(entry.first) ? "RAM" : "VRAM", ggml_backend_name(entry.first), plan.segments.size(), plan.segments.size() == 1 ? "" : "s"); } session_.logged_compute_bytes_ = std::move(peak_compute_bytes); session_.logged_segment_count_ = plan.segments.size(); } return true; } ExecutionSession::ExecutionSession(ggml_backend_t backend, const std::shared_ptr& manager) : backend_(backend), residency_manager_(manager), cache_(backend), cut_cache_(backend), workspace_(backend) { GGML_ASSERT(backend_ != nullptr); } size_t ExecutionSession::resident_bytes(ggml_backend_t backend) const { size_t bytes = workspace_.bytes(backend); if (backend == backend_) { const auto add = [](size_t a, size_t b) { return b > SIZE_MAX - a ? SIZE_MAX : a + b; }; bytes = add(bytes, cache_.resident_bytes(ggml_backend_get_device(backend))); bytes = add(bytes, cut_cache_.resident_bytes(ggml_backend_get_device(backend))); } return bytes; } void ExecutionSession::set_layer_split_enabled(bool enabled) { layer_split_enabled_ = enabled; if (!enabled) { weight_assignments_.clear(); node_assignments_.clear(); primary_notice_logged_ = false; } } void ExecutionSession::set_backend_limits(const std::vector& limits) { backend_limits_ = limits; weight_assignments_.clear(); node_assignments_.clear(); primary_notice_logged_ = false; } void ExecutionSession::set_runtime_backends(const std::vector& backends) { extra_backends_.clear(); for (auto backend : backends) { if (backend != nullptr && backend != backend_ && std::find(extra_backends_.begin(), extra_backends_.end(), backend) == extra_backends_.end()) { extra_backends_.push_back(backend); } } workspace_.set_extra_backends(extra_backends_); weight_assignments_.clear(); node_assignments_.clear(); primary_notice_logged_ = false; } ExecutionEngine::~ExecutionEngine() { for (auto& entry : sessions_) { end(*entry.second); } } ExecutionSession& ExecutionEngine::create_session(ggml_backend_t backend, const std::shared_ptr& manager) { GGML_ASSERT(manager.get() == manager_); auto session = std::make_unique(backend, manager); auto& result = *session; sessions_.emplace(session->owner_id(), std::move(session)); return result; } void ExecutionEngine::release_session(ExecutionSession& session) { end(session); sessions_.erase(session.owner_id()); } bool ExecutionEngine::start(ExecutionSession& session) { if (session.started_) { return true; } sd_backend_set_buffer_cache_enabled(session.backend_, true); for (auto backend : session.extra_backends_) { sd_backend_set_buffer_cache_enabled(backend, true); } session.workspace_.set_extra_backends(session.extra_backends_); if (auto manager = session.residency_manager_.lock()) { manager->register_runtime_owner(session.owner_id()); } session.started_ = true; return true; } void ExecutionEngine::end(ExecutionSession& session) { GGML_ASSERT(!session.executing_); if (!session.started_) { return; } session.workspace_.release(); session.cache_.clear(); session.cut_cache_.clear(); session.logged_compute_bytes_.clear(); session.logged_segment_count_ = 0; if (auto manager = session.residency_manager_.lock()) { manager->clear_prefetched_params(session.owner_id()); std::vector params(session.params_.begin(), session.params_.end()); manager->evict_compute_backend_params(params); manager->remove_runtime_owner(session.owner_id()); } session.params_.clear(); sd_backend_clear_buffer_cache(session.backend_); for (auto backend : session.extra_backends_) { sd_backend_clear_buffer_cache(backend); } session.started_ = false; } void ExecutionEngine::sync_residency(ExecutionSession& session) { if (auto manager = session.residency_manager_.lock()) { manager->update_runtime_residency(session.owner_id(), session.backend_, session.resident_bytes(session.backend_)); for (auto backend : session.extra_backends_) { manager->update_runtime_residency(session.owner_id(), backend, session.resident_bytes(backend)); } } } bool ExecutionEngine::ensure_capacity( DeviceResidencyManager& manager, const DeviceMemoryRequest& request, const std::vector& required_params, const std::vector>& preferred_eviction_order, const std::vector& protected_params) { GGML_ASSERT(&manager == manager_); auto capacity = [&]() { return manager.inspect_compute_backend_capacity(request, required_params); }; const auto initial = capacity(); if (!initial.valid) { return false; } if (initial.fits()) { return true; } for (auto& entry : sessions_) { auto& session = *entry.second; if (session.owner_id() != request.owner_id && session.workspace_.release()) { sync_residency(session); if (capacity().fits(true)) { return true; } } } auto protected_weights = protected_params; protected_weights.insert(protected_weights.end(), required_params.begin(), required_params.end()); auto candidates = manager.evictable_compute_backend_blocks(request.compute_backend, protected_weights); std::unordered_map preferred_positions; for (size_t index = 0; index < preferred_eviction_order.size(); ++index) { for (auto* tensor : preferred_eviction_order[index]) { auto* weight = manager.resolve_param_tensor(tensor); preferred_positions.emplace(weight != nullptr ? weight : tensor, index); } } auto preferred_position = [&](const WeightResidencyBlock& block) { size_t position = 0; // A shared allocation follows the last of its members in the eviction order. for (auto* tensor : block.tensors) { const auto found = preferred_positions.find(tensor); if (found == preferred_positions.end()) { return SIZE_MAX; } position = std::max(position, found->second); } return position; }; std::stable_sort(candidates.begin(), candidates.end(), [&](const auto& lhs, const auto& rhs) { if (lhs.requires_demotion != rhs.requires_demotion) { return !lhs.requires_demotion; } if (!lhs.requires_demotion) { const auto left = preferred_position(lhs), right = preferred_position(rhs); if (left != right) { return left < right; } if (left != SIZE_MAX) { return false; } } return lhs.last_use_epoch < rhs.last_use_epoch; }); for (const auto& block : candidates) { if (manager.evict_compute_backend_block(block) && capacity().fits(true)) { return true; } } auto available = capacity(); if (available.fits(true)) { return true; } if (sd_backend_is(request.compute_backend, "Vulkan") && available.required_budget_bytes <= available.available_budget_bytes) { ggml_backend_synchronize(request.compute_backend); sd_backend_clear_buffer_cache(request.compute_backend); const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); do { available = capacity(); if (available.fits(true)) { return true; } if (available.required_budget_bytes > available.available_budget_bytes) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } while (std::chrono::steady_clock::now() < deadline); } available = manager.inspect_compute_backend_capacity(request, required_params, true); const auto device_bytes = available.available_device_bytes == SIZE_MAX ? std::string("unknown") : sd_format("%.2f MB", available.available_device_bytes / (1024.0 * 1024.0)); const auto budget_bytes = available.available_budget_bytes == SIZE_MAX ? std::string("unlimited") : sd_format("%.2f MB", available.available_budget_bytes / (1024.0 * 1024.0)); LOG_WARN("execution engine cannot make enough memory available on %s: need %.2f MB device / %.2f MB budget, available %s device / %s budget", ggml_backend_name(request.compute_backend), available.required_device_bytes / (1024.0 * 1024.0), available.required_budget_bytes / (1024.0 * 1024.0), device_bytes.c_str(), budget_bytes.c_str()); return false; } void ExecutionEngine::clear_cache(ExecutionSession& session) { session.cache_.clear(); sync_residency(session); } void ExecutionEngine::discard_graph(ExecutionSession& session) { GGML_ASSERT(!session.executing_); session.cache_.graph_end(false); session.cut_cache_.clear(); session.workspace_.release(); sync_residency(session); } ComputeResult ExecutionEngine::execute(ExecutionSession& session, const ComputeTask& task) { if (session.executing_ || task.graph == nullptr || !start(session)) { return {}; } session.executing_ = true; ComputeResult result; try { GraphExecution execution(*this, session, task); result = execution.run(); } catch (const std::bad_alloc&) { result.status = GGML_STATUS_ALLOC_FAILED; LOG_ERROR("%s graph allocation failed", task.description.c_str()); } catch (const std::exception& error) { LOG_ERROR("%s graph execution failed on %s: %s", task.description.c_str(), ggml_backend_name(session.backend_), error.what()); } catch (...) { LOG_ERROR("%s graph execution failed on %s: unknown exception", task.description.c_str(), ggml_backend_name(session.backend_)); } if (!session.workspace_.segment_end()) { result.status = GGML_STATUS_FAILED; } const bool success = result.status == GGML_STATUS_SUCCESS; session.cache_.graph_end(success); session.cut_cache_.clear(); if (!success) { result.output = {}; session.workspace_.release(); } session.executing_ = false; sync_residency(session); return result; } }