perf: optimize Vulkan kernels, graph fusion, and workspace reuse (#2251)

This commit is contained in:
leejet authored and GitHub committed 2026-10-31 14:55:58 +08:00
1 parent e239e4b0fd
commit 0a067120b3
6 files changed
+89 -17

No files matched your search

+3
View File
@@ -247,6 +247,9 @@ These are backend-level defaults. GGML still chooses the actual kernel based on
weight type, shape, and hardware; enabling direct convolution does not guarantee weight type, shape, and hardware; enabling direct convolution does not guarantee
that every operation uses implicit GEMM. The CUDA-family implicit GEMM paths that every operation uses implicit GEMM. The CUDA-family implicit GEMM paths
require F16 weights and can use ordinary direct kernels for other cases. require F16 weights and can use ordinary direct kernels for other cases.
For 3D convolution, a supported direct kernel is also used as a fallback when
the im2col decomposition is unsupported for the actual tensor sizes, including
when an intermediate reshape or matrix multiplication exceeds backend limits.
This option does not change the separately created image upscaler context. This option does not change the separately created image upscaler context.
Repeating `--conv-direct` replaces the previous assignment string. Existing Repeating `--conv-direct` replaces the previous assignment string. Existing
+1 -1
Submodule ggml updated: 7b2b435da0...6a89493756.
+54 -1
View File
@@ -92,7 +92,8 @@ namespace sd {
ComputeWorkspace::Measurement ComputeWorkspace::measure( ComputeWorkspace::Measurement ComputeWorkspace::measure(
ggml_cgraph* graph, ggml_cgraph* graph,
const std::function<ggml_backend_t(const ggml_tensor*)>& external_backend, const std::function<ggml_backend_t(const ggml_tensor*)>& external_backend,
const AssignNodes& assign_nodes) { const AssignNodes& assign_nodes,
bool cache_measurement) {
std::vector<const ggml_tensor*> tensors; std::vector<const ggml_tensor*> tensors;
std::unordered_set<const ggml_tensor*> seen; std::unordered_set<const ggml_tensor*> seen;
auto visit = [&](const ggml_tensor* tensor) { auto visit = [&](const ggml_tensor* tensor) {
@@ -112,6 +113,52 @@ namespace sd {
visit(source); visit(source);
} }
} }
std::vector<uint8_t> key;
if (cache_measurement) {
key.reserve(tensors.size() * sizeof(ggml_tensor));
auto append = [&](const auto& value) {
const auto* bytes = reinterpret_cast<const uint8_t*>(&value);
key.insert(key.end(), bytes, bytes + sizeof(value));
};
append(graph->n_nodes);
append(graph->n_leafs);
append(graph->order);
for (auto tensor : tensors) {
append(tensor);
append(tensor->type);
append(tensor->ne);
append(tensor->nb);
append(tensor->op);
append(tensor->op_params);
append(tensor->flags);
append(tensor->src);
append(tensor->view_src);
append(tensor->view_offs);
append(tensor->name);
const auto external = external_backend(tensor);
append(external);
if (external == nullptr || tensor->view_src != nullptr) {
append(tensor->buffer);
append(tensor->data);
append(tensor->extra);
if (tensor->buffer != nullptr) {
const auto usage = ggml_backend_buffer_get_usage(tensor->buffer);
const auto type = ggml_backend_buffer_get_type(tensor->buffer);
append(usage);
append(type);
}
}
const size_t index = ggml_hash_find(&graph->visited_hash_set, tensor);
const int32_t uses = index != GGML_HASHSET_FULL && ggml_bitset_get(graph->visited_hash_set.used, index)
? graph->use_counts[index]
: 0;
append(uses);
}
// Match the full allocation metadata, including topology and external placement.
if (key == measurement_key_) {
return {measured_buffers_};
}
}
const size_t graph_size = tensors.size() + 8; const size_t graph_size = tensors.size() + 8;
auto context = ggml_init({tensors.size() * ggml_tensor_overhead() + ggml_graph_overhead_custom(graph_size, false), nullptr, true}); auto context = ggml_init({tensors.size() * ggml_tensor_overhead() + ggml_graph_overhead_custom(graph_size, false), nullptr, true});
if (context == nullptr) { if (context == nullptr) {
@@ -178,6 +225,10 @@ namespace sd {
ggml_backend_buffer_free(entry.second); ggml_backend_buffer_free(entry.second);
} }
ggml_free(context); ggml_free(context);
if (cache_measurement && !result.buffers.empty()) {
measurement_key_ = std::move(key);
measured_buffers_ = result.buffers;
}
return result; return result;
} }
@@ -212,6 +263,8 @@ namespace sd {
if (active_) { if (active_) {
return false; return false;
} }
measurement_key_.clear();
measured_buffers_.clear();
ggml_backend_sched_free(scheduler_); ggml_backend_sched_free(scheduler_);
scheduler_ = nullptr; scheduler_ = nullptr;
scheduler_capacity_ = 0; scheduler_capacity_ = 0;
+4 -1
View File
@@ -19,6 +19,8 @@ namespace sd {
ggml_backend_sched_t scheduler_ = nullptr; ggml_backend_sched_t scheduler_ = nullptr;
size_t scheduler_capacity_ = 0; size_t scheduler_capacity_ = 0;
bool active_ = false; bool active_ = false;
std::vector<uint8_t> measurement_key_;
std::vector<BackendBufferSize> measured_buffers_;
ggml_backend_sched_t make_scheduler(size_t graph_size); ggml_backend_sched_t make_scheduler(size_t graph_size);
@@ -41,7 +43,8 @@ namespace sd {
Measurement measure( Measurement measure(
ggml_cgraph* graph, ggml_cgraph* graph,
const std::function<ggml_backend_t(const ggml_tensor*)>& external_backend, const std::function<ggml_backend_t(const ggml_tensor*)>& external_backend,
const AssignNodes& assign_nodes); const AssignNodes& assign_nodes,
bool cache_measurement = false);
void synchronize() const; void synchronize() const;
bool segment_end() noexcept; bool segment_end() noexcept;
bool release(); bool release();
+26 -13
View File
@@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include <vector>
#include "core/ggml_extend_backend.h" #include "core/ggml_extend_backend.h"
@@ -478,21 +479,33 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
x = ggml_cont(ctx, ggml_permute(ctx, x, 0, 1, 3, 2)); x = ggml_cont(ctx, ggml_permute(ctx, x, 0, 1, 3, 2));
x = ggml_reshape_4d(ctx, x, im2col->ne[1], im2col->ne[2], OD, OC * N); x = ggml_reshape_4d(ctx, x, im2col->ne[1], im2col->ne[2], OD, OC * N);
} else { } else {
// ggml_conv_3d decomposes into GGML_OP_IM2COL_3D, which some backends auto convolution = ggml_conv_3d(ctx, w, x, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2);
// (e.g. Metal, see #850) do not implement. Fall back to
// GGML_OP_CONV_3D on those backends.
bool im2col_3d_supported = true;
if (backend != nullptr) { if (backend != nullptr) {
ggml_tensor* im2col = ggml_im2col_3d(ctx, w, x, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, w->type); // im2col can support large buffers that its reshape/GEMM consumers cannot.
im2col_3d_supported = ggml_backend_supports_op(backend, im2col); std::vector<ggml_tensor*> nodes{convolution};
} bool supported = true;
if (im2col_3d_supported) { for (size_t i = 0; i < nodes.size() && supported; ++i) {
x = ggml_conv_3d(ctx, w, x, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2); auto node = nodes[i];
} else { if (node == x || node == w) {
int64_t OC = w->ne[3] / IC; continue;
int64_t N = x->ne[3] / IC; }
x = ggml_conv_3d_direct(ctx, w, x, s0, s1, s2, p0, p1, p2, d0, d1, d2, (int)IC, (int)N, (int)OC); supported = ggml_backend_supports_op(backend, node);
for (auto source : node->src) {
if (source != nullptr) {
nodes.push_back(source);
}
}
}
if (!supported) {
int64_t OC = w->ne[3] / IC;
int64_t N = x->ne[3] / IC;
auto fallback = ggml_conv_3d_direct(ctx, w, x, s0, s1, s2, p0, p1, p2, d0, d1, d2, (int)IC, (int)N, (int)OC);
if (ggml_backend_supports_op(backend, fallback)) {
convolution = fallback;
}
}
} }
x = convolution;
} }
if (scale != 1.f) { if (scale != 1.f) {
+1 -1
View File
@@ -735,7 +735,7 @@ ComputeWorkspace::Measurement GGMLRunner::measure(ggml_cgraph* graph, const std:
auto assign_nodes = [&](ggml_backend_sched_t scheduler, ggml_cgraph* copy) { auto assign_nodes = [&](ggml_backend_sched_t scheduler, ggml_cgraph* copy) {
pin_multi_device_nodes(scheduler, copy, graph); pin_multi_device_nodes(scheduler, copy, graph);
}; };
return workspace_.measure(graph, external_backend, assign_nodes); return workspace_.measure(graph, external_backend, assign_nodes, !is_multi_device());
} }
std::vector<DeviceMemoryRequest> GGMLRunner::memory_requests( std::vector<DeviceMemoryRequest> GGMLRunner::memory_requests(