perf: optimize H3 inference and segmented weight prefetch (#2240)

This commit is contained in:
leejet authored and GitHub committed 2026-10-30 00:38:14 +08:00
1 parent e7b7ec6d6f
commit 37bc88b5e9
17 files changed
+367 -210

No files matched your search

+79 -28
View File
@@ -466,6 +466,10 @@ bool GGMLRunner::runner_start() {
if (runner_started_) {
return true;
}
sd_backend_set_buffer_cache_enabled(runtime_backend, true);
for (auto backend : extra_runtime_backends) {
sd_backend_set_buffer_cache_enabled(backend, true);
}
cache_.clear();
workspace_.set_extra_backends(extra_runtime_backends);
if (auto manager = residency_manager.lock()) {
@@ -490,6 +494,7 @@ void GGMLRunner::runner_end() {
free_compute_ctx();
compute_ctx_buffer_.reset();
cache_.clear();
cut_cache_.clear();
logged_compute_bytes_.clear();
logged_segment_count_ = 0;
if (auto manager = residency_manager.lock()) {
@@ -503,6 +508,10 @@ void GGMLRunner::runner_end() {
manager->evict_compute_backend_params(tensors);
manager->remove_runtime_owner(reinterpret_cast<uintptr_t>(this));
}
sd_backend_clear_buffer_cache(runtime_backend);
for (auto backend : extra_runtime_backends) {
sd_backend_clear_buffer_cache(backend);
}
runner_started_ = false;
}
@@ -712,8 +721,11 @@ static size_t add_bytes(size_t a, size_t b) {
return b > SIZE_MAX - a ? SIZE_MAX : a + b;
}
ComputeWorkspace::Measurement GGMLRunner::measure(ggml_cgraph* graph) {
ComputeWorkspace::Measurement GGMLRunner::measure(ggml_cgraph* graph, const std::unordered_set<const ggml_tensor*>* external_outputs) {
auto external_backend = [&](const ggml_tensor* tensor) -> ggml_backend_t {
if (external_outputs != nullptr && external_outputs->count(tensor)) {
return runtime_backend;
}
if (!params_tensor_set_.count(tensor)) {
return nullptr;
}
@@ -766,7 +778,7 @@ bool GGMLRunner::fits(const std::vector<DeviceMemoryRequest>& requests,
return true;
}
bool GGMLRunner::execute_segment(ggml_cgraph* graph, int n_threads) {
bool GGMLRunner::execute_segment(ggml_cgraph* graph, int n_threads, const std::function<void()>& on_before_compute) {
if (sd_backend_is_cpu(runtime_backend)) {
sd_backend_cpu_set_n_threads(runtime_backend, n_threads);
}
@@ -776,7 +788,27 @@ bool GGMLRunner::execute_segment(ggml_cgraph* graph, int n_threads) {
auto scheduler = workspace_.scheduler();
GGML_ASSERT(scheduler != nullptr);
ggml_backend_sched_set_eval_callback(scheduler, sd_get_backend_eval_callback(), sd_get_backend_eval_callback_data());
const auto status = ggml_backend_sched_graph_compute(scheduler, graph);
#ifndef SD_USE_UPSTREAM_GGML
struct BeforeComputeCallback {
ggml_backend_t backend;
const std::function<void()>& callback;
bool invoked = false;
} before_compute{runtime_backend, on_before_compute};
ggml_backend_sched_set_compute_callback(
scheduler, [](ggml_backend_t backend, void* data) {
auto& hook = *static_cast<BeforeComputeCallback*>(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
workspace_.synchronize();
if (status != GGML_STATUS_SUCCESS) {
last_compute_status_ = status;
@@ -828,27 +860,31 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
if (!assign_graph_cut_layer_split_backends(graph)) {
return std::nullopt;
}
const auto params = collect_used_param_tensors(graph);
const auto& cached_plan = resolve_graph_cut_plan(graph);
const auto full_measurement = measure(graph);
if (full_measurement.buffers.empty()) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return std::nullopt;
}
auto fits_monolithic = [&]() {
// 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, cache_.pending_bytes(graph));
for (auto& request : requests) {
request.pending_allocation_bytes = add_bytes(request.pending_allocation_bytes, planning_headroom);
const auto params = collect_used_param_tensors(graph);
const auto& cached_plan = resolve_graph_cut_plan(graph);
auto manager = residency_manager.lock();
const bool can_segment = !is_multi_device() && !sd_backend_is_cpu(runtime_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({{runtime_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 std::nullopt;
}
return fits(requests, params);
};
auto manager = residency_manager.lock();
const bool segmented = !is_multi_device() && !sd_backend_is_cpu(runtime_backend) &&
manager != nullptr && manager->segmented_compute_enabled() &&
cached_plan.valid && cached_plan.has_cuts && cached_plan.segments.size() > 1 &&
!fits_monolithic();
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, 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();
@@ -922,14 +958,24 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
}
} segment_cleanup{*this, weights, bindings, segment_context};
auto measurement = segmented ? measure(segment_graph) : full_measurement;
std::unordered_set<const ggml_tensor*> 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(runtime_backend, tensor)) {
cache_outputs.insert(tensor);
}
}
}
auto measurement = segmented ? measure(segment_graph, &cache_outputs) : full_measurement;
if (!workspace_.prepare(measurement)) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return fail_segment("workspace preparation");
}
const size_t cut_bytes = last ? 0 : cut_cache_.estimate_output_bytes(graph, segment);
const size_t new_cache_bytes = add_bytes(cut_bytes, cache_.pending_bytes(segment_graph));
auto ensure_capacity = [&]() {
const size_t cut_bytes = last ? 0 : cut_cache_.estimate_output_bytes(graph, segment);
size_t new_cache_bytes = add_bytes(cut_bytes, 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))) {
@@ -956,6 +1002,11 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
if (!ensure_capacity()) {
return fail_segment("workspace capacity check");
}
if (!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 : cut_cache_.estimate_output_bytes(graph, segment), cache_.pending_bytes(segment_graph));
if (!workspace_.allocate(segment_graph, [&](ggml_backend_sched_t scheduler, ggml_cgraph* current) {
pin_multi_device_nodes(scheduler, current);
})) {
@@ -978,7 +1029,7 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
}
LOG_DEBUG("%s executing segment %zu/%zu: %s", get_desc().c_str(),
index + 1, plan.segments.size(), segment.group_name.c_str());
if (!execute_segment(segment_graph, n_threads)) {
if (!execute_segment(segment_graph, n_threads, [&]() { weights.submit_next(); })) {
return fail_segment("execution");
}
auto cache_status = cache_.capture(segment_graph);