From 46da352af0f16a86a1993e1cc938acb4d1e43a34 Mon Sep 17 00:00:00 2001 From: Windyskr <54269955+Windyskr@users.noreply.github.com> Date: Mon, 9 Nov 2026 23:40:26 +0800 Subject: [PATCH] fix: size layer-split ranges in proportion to device capacity (#2309) --- src/core/layer_split_partition.cpp | 63 +++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/core/layer_split_partition.cpp b/src/core/layer_split_partition.cpp index ff8aaeb6..67b92ee4 100644 --- a/src/core/layer_split_partition.cpp +++ b/src/core/layer_split_partition.cpp @@ -73,10 +73,13 @@ namespace sd { return backend_index == 0 ? primary_backend_vram_limit : 0; } + // Capacity of a device that reports no free memory and has no VRAM limit. + static constexpr int64_t graph_cut_layer_split_unknown_capacity = std::numeric_limits::max() / 4; + static std::vector graph_cut_layer_split_backend_capacities(const std::vector& backends, const std::vector& backend_vram_limits, size_t primary_backend_vram_limit) { - std::vector capacities(backends.size(), std::numeric_limits::max() / 4); + std::vector capacities(backends.size(), graph_cut_layer_split_unknown_capacity); constexpr int64_t compute_headroom_bytes = 2ll * 1024 * 1024 * 1024; for (size_t i = 0; i < backends.size(); i++) { ggml_backend_dev_t dev = ggml_backend_get_device(backends[i]); @@ -97,6 +100,57 @@ namespace sd { return capacities; } + // Whether filling the devices in order, each up to `fraction` of its capacity, places every + // segment. Mirrors the placement loop in partition_graph_cut_layer_split(). + static bool graph_cut_layer_split_fits(const std::vector& segment_bytes, + const std::vector& capacities, + double fraction) { + size_t backend = 0; + int64_t used = 0; + for (int64_t bytes : segment_bytes) { + while (backend + 1 < capacities.size() && bytes > 0 && + used + bytes > (int64_t)((double)capacities[backend] * fraction)) { + backend++; + used = 0; + } + if (bytes > 0 && used + bytes > (int64_t)((double)capacities[backend] * fraction)) { + return false; + } + used += bytes; + } + return true; + } + + // Fill limits that size the contiguous ranges in proportion to the device capacities: the same + // fraction of every capacity, as small as possible while all segments still fit. Filling the + // first device up to its capacity would leave only the fixed headroom for compute, which is too + // small for large video graphs. A fraction of 1 is plain capacity-based placement, so every + // layout that fits the capacities is still found. + static std::vector graph_cut_layer_split_fill_limits(const std::vector& segment_bytes, + const std::vector& capacities) { + const bool capacities_known = std::all_of(capacities.begin(), capacities.end(), [](int64_t capacity) { + return capacity < graph_cut_layer_split_unknown_capacity; + }); + if (!capacities_known || !graph_cut_layer_split_fits(segment_bytes, capacities, 1.0)) { + return capacities; + } + double low = 0.0; + double high = 1.0; + for (int i = 0; i < 32; i++) { + const double mid = (low + high) / 2; + if (graph_cut_layer_split_fits(segment_bytes, capacities, mid)) { + high = mid; + } else { + low = mid; + } + } + std::vector limits(capacities.size()); + for (size_t i = 0; i < capacities.size(); i++) { + limits[i] = (int64_t)((double)capacities[i] * high); + } + return limits; + } + bool partition_graph_cut_layer_split(const char* desc, ggml_cgraph* gf, const sd::ggml_graph_cut::Plan& plan, @@ -151,6 +205,11 @@ namespace sd { return param_assignments.count(param) != 0; }); + // Move to the next device at the proportional fill limit; the capacity stays the hard limit. + const std::vector backend_limits = reuse_assignments + ? backend_capacities + : graph_cut_layer_split_fill_limits(segment_param_bytes, backend_capacities); + std::vector backend_by_segment(plan.segments.size(), split_backends[0]); size_t current_backend = 0; int64_t current_used = 0; @@ -158,7 +217,7 @@ namespace sd { int64_t bytes = segment_param_bytes[seg_idx]; while (!reuse_assignments && current_backend + 1 < split_backends.size() && bytes > 0 && - current_used + bytes > backend_capacities[current_backend]) { + current_used + bytes > backend_limits[current_backend]) { current_backend++; current_used = 0; }