Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ add_subdirectory(thirdparty)

target_sources(${SD_LIB} PRIVATE $<TARGET_OBJECTS:zip>)
target_link_libraries(${SD_LIB} PUBLIC ggml)
find_package(Threads REQUIRED)
target_link_libraries(${SD_LIB} PRIVATE Threads::Threads)
target_link_libraries(${SD_LIB} PRIVATE onig sd-utf8proc)
if (SD_CUDA)
find_package(CUDAToolkit REQUIRED)
Expand Down
3 changes: 2 additions & 1 deletion cmake/stable-diffusion-config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(SD_BIN_DIR "@PACKAGE_SD_BIN_INSTALL_DIR@")

include(CMakeFindDependencyMacro)
find_dependency(ggml REQUIRED HINTS "${SD_LIB_DIR}/cmake")
find_dependency(Threads REQUIRED)
if(@SD_CUDA@)
find_dependency(CUDAToolkit REQUIRED)
endif()
Expand All @@ -25,7 +26,7 @@ if(NOT TARGET stable-diffusion)
set_target_properties(stable-diffusion
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SD_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "ggml::ggml"
INTERFACE_LINK_LIBRARIES "ggml::ggml;Threads::Threads"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${stable-diffusion_LIBRARY}"
INTERFACE_COMPILE_FEATURES "c_std_11;cxx_std_17"
Expand Down
2 changes: 1 addition & 1 deletion cmake/stable-diffusion.pc.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ Name: stable-diffusion
Description: Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++
Version: @SDCPP_BUILD_VERSION@
Libs: -L${libdir} -lstable-diffusion
Libs.private: -lggml -lggml-base
Libs.private: -lggml -lggml-base @CMAKE_THREAD_LIBS_INIT@
Cflags: -I${includedir}
143 changes: 143 additions & 0 deletions src/core/parallel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "core/parallel.h"

#include <algorithm>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <thread>
#include <vector>

namespace sd {

namespace parallel_detail {
thread_local ParallelExecutor* executor = nullptr;
thread_local bool active = false;
}

struct ParallelExecutor::Impl {
struct Worker {
std::condition_variable wake;
std::thread thread;
bool ready = false;
};

ParallelExecutor* owner;
std::mutex invocation_mutex;
std::mutex mutex;
std::condition_variable finished;
std::vector<std::unique_ptr<Worker>> workers;
bool stopping = false;
int pending = 0;
int participants = 1;
int64_t begin = 0;
int64_t count = 0;
const std::function<void(int64_t, int64_t)>* task = nullptr;
std::exception_ptr error;

explicit Impl(ParallelExecutor* owner)
: owner(owner) {}

~Impl() {
{
std::lock_guard<std::mutex> lock(mutex);
stopping = true;
}
for (auto& worker : workers) {
worker->wake.notify_one();
}
for (auto& worker : workers) {
worker->thread.join();
}
}

void execute(int index) {
ParallelScope scope(owner);
parallel_detail::Region region;
const int64_t size = count / participants;
const int64_t extra = count % participants;
const int64_t first = begin + index * size + std::min<int64_t>(index, extra);
const int64_t last = first + size + (index < extra ? 1 : 0);
try {
(*task)(first, last);
} catch (...) {
std::lock_guard<std::mutex> lock(mutex);
if (!error) {
error = std::current_exception();
}
}
}

void worker_loop(Worker* worker, int index) {
std::unique_lock<std::mutex> lock(mutex);
for (;;) {
worker->wake.wait(lock, [&] { return stopping || worker->ready; });
if (stopping) {
return;
}
worker->ready = false;
lock.unlock();
execute(index);
lock.lock();
if (--pending == 0) {
finished.notify_one();
}
}
}

void run(int64_t first, int64_t last, int n_tasks, const std::function<void(int64_t, int64_t)>& callback) {
std::lock_guard<std::mutex> invocation_lock(invocation_mutex);
std::unique_lock<std::mutex> lock(mutex);
while (static_cast<int>(workers.size()) < n_tasks - 1) {
workers.push_back(std::make_unique<Worker>());
auto* worker = workers.back().get();
const int index = static_cast<int>(workers.size());
try {
worker->thread = std::thread([this, worker, index] { worker_loop(worker, index); });
} catch (...) {
workers.pop_back();
throw;
}
}
begin = first;
count = last - first;
participants = n_tasks;
pending = n_tasks - 1;
task = &callback;
error = nullptr;
for (int i = 0; i < pending; ++i) {
workers[i]->ready = true;
workers[i]->wake.notify_one();
}
lock.unlock();
execute(0);
lock.lock();
finished.wait(lock, [&] { return pending == 0; });
task = nullptr;
if (error) {
std::rethrow_exception(error);
}
}
};

ParallelExecutor::ParallelExecutor(int n_threads)
: n_threads_(std::max(1, n_threads)), impl_(std::make_unique<Impl>(this)) {}

ParallelExecutor::~ParallelExecutor() = default;

void ParallelExecutor::run(int64_t begin, int64_t end, int64_t grain_size, const std::function<void(int64_t, int64_t)>& task) {
if (begin < 0 || grain_size <= 0) {
throw std::invalid_argument("parallel_for requires begin >= 0 and grain_size > 0");
}
if (end <= begin) {
return;
}
const int n_tasks = static_cast<int>(std::min<int64_t>(n_threads_, (end - begin) / grain_size));
if (parallel_detail::active || n_tasks <= 1) {
parallel_detail::Region region;
task(begin, end);
return;
}
impl_->run(begin, end, n_tasks, task);
}

}
77 changes: 77 additions & 0 deletions src/core/parallel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef __SD_CORE_PARALLEL_H__
#define __SD_CORE_PARALLEL_H__

#include <cstdint>
#include <functional>
#include <memory>
#include <stdexcept>
#include <utility>

namespace sd {

class ParallelExecutor {
struct Impl;
int n_threads_;
std::unique_ptr<Impl> impl_;

public:
explicit ParallelExecutor(int n_threads);
~ParallelExecutor();
ParallelExecutor(const ParallelExecutor&) = delete;
ParallelExecutor& operator=(const ParallelExecutor&) = delete;

int num_threads() const { return n_threads_; }
void run(int64_t begin, int64_t end, int64_t grain_size, const std::function<void(int64_t, int64_t)>& task);
};

namespace parallel_detail {
extern thread_local ParallelExecutor* executor;
extern thread_local bool active;

class Region {
bool previous_;

public:
Region()
: previous_(active) { active = true; }
~Region() { active = previous_; }
Region(const Region&) = delete;
Region& operator=(const Region&) = delete;
};
}

class ParallelScope {
ParallelExecutor* previous_;

public:
explicit ParallelScope(ParallelExecutor* executor)
: previous_(parallel_detail::executor) {
parallel_detail::executor = executor;
}
~ParallelScope() { parallel_detail::executor = previous_; }
ParallelScope(const ParallelScope&) = delete;
ParallelScope& operator=(const ParallelScope&) = delete;
};

// Ranges are non-negative. The callback may run concurrently and must own its writes.
template <typename F>
inline void parallel_for(int64_t begin, int64_t end, int64_t grain_size, F&& task) {
if (begin < 0 || grain_size <= 0) {
throw std::invalid_argument("parallel_for requires begin >= 0 and grain_size > 0");
}
if (end <= begin) {
return;
}
auto* executor = parallel_detail::executor;
if (parallel_detail::active || executor == nullptr || executor->num_threads() <= 1 ||
(end - begin) / grain_size < 2) {
parallel_detail::Region region;
task(begin, end);
return;
}
executor->run(begin, end, grain_size, std::forward<F>(task));
}

}

#endif // __SD_CORE_PARALLEL_H__
Loading
Loading