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
15 changes: 3 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ option(SD_MUSA "sd: musa backend" OFF)
option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF)
option(SD_BUILD_SHARED_GGML_LIB "sd: build ggml as a separate shared lib" OFF)
option(SD_USE_SYSTEM_GGML "sd: use system-installed GGML library" OFF)
option(SD_USE_UPSTREAM_GGML "sd: build with upstream GGML instead of the patched GGML extensions" OFF)
set(SD_GGML_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ggml" CACHE PATH "sd: ggml source directory (also supplies private headers for system ggml)")
#option(SD_BUILD_SERVER "sd: build server example" ON)

set(CMAKE_C_STANDARD 11)
Expand Down Expand Up @@ -325,18 +327,7 @@ if (NOT SD_USE_SYSTEM_GGML)
endif()

# deps
# Only add ggml if it hasn't been added yet
if (NOT TARGET ggml)
if (SD_USE_SYSTEM_GGML)
find_package(ggml REQUIRED)
if (NOT ggml_FOUND)
message(FATAL_ERROR "System-installed GGML library not found.")
endif()
add_library(ggml ALIAS ggml::ggml)
else()
add_subdirectory(ggml)
endif()
endif()
include(cmake/ggml.cmake)

add_subdirectory(thirdparty)

Expand Down
27 changes: 27 additions & 0 deletions cmake/ggml.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
if(NOT TARGET ggml AND NOT TARGET ggml::ggml)
if(SD_USE_SYSTEM_GGML)
find_package(ggml REQUIRED)
else()
add_subdirectory("${SD_GGML_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ggml")
endif()
endif()
if(NOT TARGET ggml)
add_library(ggml ALIAS ggml::ggml)
endif()

get_target_property(sd_ggml_imported ggml IMPORTED)
if(sd_ggml_imported)
set(sd_ggml_private_include "${SD_GGML_SOURCE_DIR}/src")
else()
get_target_property(sd_ggml_private_include ggml SOURCE_DIR)
endif()
if(NOT EXISTS "${sd_ggml_private_include}/ggml-impl.h")
message(FATAL_ERROR "Set SD_GGML_SOURCE_DIR to the source tree matching the selected ggml library (ggml-impl.h is required).")
endif()
target_include_directories(${SD_LIB} PRIVATE "${sd_ggml_private_include}")
set_property(TARGET ${SD_LIB} PROPERTY SD_GGML_PRIVATE_INCLUDE_DIR "${sd_ggml_private_include}")

if(SD_USE_UPSTREAM_GGML)
target_compile_definitions(${SD_LIB} PUBLIC SD_USE_UPSTREAM_GGML)
message(WARNING "Using upstream GGML: FP8 and INT8 tensorwise/convrot are disabled. Some operators may be unsupported and performance may be lower than with patched GGML.")
endif()
32 changes: 32 additions & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,38 @@ git submodule init
git submodule update
```

## Selecting a GGML source tree

By default, sd.cpp builds the patched GGML submodule in `ggml/`. To build with
an upstream GGML checkout instead, enable `SD_USE_UPSTREAM_GGML` and set
`SD_GGML_SOURCE_DIR`:

```shell
cmake -S . -B build-upstream -DSD_USE_UPSTREAM_GGML=ON -DSD_GGML_SOURCE_DIR=../ggml-upstream
cmake --build build-upstream --config Release
```

The selected source tree supplies both the library and its private headers.
Backend options such as `-DSD_CUDA=ON` apply to the selected tree as usual.

`SD_USE_UPSTREAM_GGML` defaults to `OFF`, which enables the patched GGML
extensions. Set it to `ON` when using upstream GGML; it selects the compatibility
mode and does not download or replace the GGML source tree. Upstream mode
disables FP8 and INT8 tensorwise/convrot and rejects their model files with an
explicit error. FP8 weight type requests, tensor type rules and conversion
outputs are also rejected; no automatic conversion is performed.

Upstream GGML may lack some operators and performance optimizations provided by
the patched version. A warning is emitted during CMake configuration and when
creating an inference context. Ordinary floating-point and shared GGML
quantization types remain available, subject to backend operator support.

`SD_USE_SYSTEM_GGML=ON` instead links an installed GGML CMake package, located
with `ggml_DIR` or `CMAKE_PREFIX_PATH`. In that mode, `SD_GGML_SOURCE_DIR` must
point to the matching source tree for private headers. The installed library
must use the same ABI settings as sd.cpp, including `GGML_MAX_NAME`.
Set `SD_USE_UPSTREAM_GGML=ON` as well if the installed package is upstream GGML.

## WebP and WebM Support in Examples

The example applications (`examples/cli` and `examples/server`) use `libwebp` to support WebP image I/O, and `examples/cli` can also use `libwebm` for `.webm` video output. Both are enabled by default. WebM output currently reuses `libwebp` to encode each frame as VP8 before muxing with `libwebm`.
Expand Down
3 changes: 3 additions & 0 deletions docs/int8_convrot.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

sd.cpp can load and execute ComfyUI `int8_tensorwise` safetensors with `convrot` metadata directly. The stored INT8 weights are not converted to another weight type at load time.

This requires the INT8 tensorwise/convrot extensions in the patched GGML.
Builds with `SD_USE_UPSTREAM_GGML=ON` reject these files during loading.

## Checkpoint format

Each quantized linear module contains the following tensors:
Expand Down
3 changes: 3 additions & 0 deletions src/convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,9 @@ bool convert_with_components(const char* model_path,
const char* tensor_type_rules,
bool convert_name,
int n_threads) {
if (!validate_tensor_types(output_type, tensor_type_rules)) {
return false;
}
ModelLoader model_loader;
bool loaded_any = false;

Expand Down
2 changes: 1 addition & 1 deletion src/core/compute_workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "core/ggml_graph_cut.h"
#include "core/util.h"
#include "ggml-cpu.h"
#include "ggml/src/ggml-impl.h"
#include "ggml-impl.h"

namespace sd {
ComputeWorkspace::~ComputeWorkspace() {
Expand Down
12 changes: 12 additions & 0 deletions src/core/ggml_extend.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "core/ggml_extend.h"

#include <cmath>
#include <stdexcept>
#include <utility>

#include "core/ggml_extend_backend.h"
Expand Down Expand Up @@ -247,6 +248,7 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
ggml_tensor* b,
int convrot_group_size,
float scale) {
#ifndef SD_USE_UPSTREAM_GGML
GGML_ASSERT(x->type == GGML_TYPE_F32 || (x->type == GGML_TYPE_I8 && scale == 1.f));
if (scale != 1.f) {
x = ggml_ext_scale(ctx, x, scale);
Expand All @@ -270,6 +272,16 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
}
}
return x;
#else
GGML_UNUSED(ctx);
GGML_UNUSED(x);
GGML_UNUSED(w);
GGML_UNUSED(weight_scale);
GGML_UNUSED(b);
GGML_UNUSED(convrot_group_size);
GGML_UNUSED(scale);
throw std::runtime_error("INT8 tensorwise/convrot is not supported by this ggml build");
#endif
}

ggml_tensor* ggml_ext_pad_ext(ggml_context* ctx,
Expand Down
2 changes: 1 addition & 1 deletion src/core/ggml_extend_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#endif

#include "core/util.h"
#include "ggml/src/ggml-impl.h"
#include "ggml-impl.h"
#include "stable-diffusion.h"

static std::string trim_copy(const std::string& value) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/ggml_graph_cut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#include "ggml-alloc.h"
#include "ggml-backend.h"

#include "ggml/src/ggml-impl.h"
#include "ggml-impl.h"

namespace sd::ggml_graph_cut {

Expand Down
31 changes: 30 additions & 1 deletion src/core/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,14 +414,43 @@ std::vector<std::string> split_string(const std::string& str, char delimiter) {
}

ggml_type sd_type_to_ggml_type(sd_type_t sdtype) {
if (sdtype == SD_TYPE_F8_E4M3 || sdtype == SD_TYPE_F8_E5M2) {
#ifndef SD_USE_UPSTREAM_GGML
return sdtype == SD_TYPE_F8_E4M3 ? GGML_TYPE_F8_E4M3 : GGML_TYPE_F8_E5M2;
#else
return GGML_TYPE_COUNT;
#endif
}
const int type_value = static_cast<int>(sdtype);
if (type_value < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
if (type_value >= 0 && type_value < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
return static_cast<ggml_type>(type_value);
} else {
return GGML_TYPE_COUNT;
}
}

bool validate_tensor_types(sd_type_t type, const char* tensor_type_rules) {
if (type != SD_TYPE_COUNT && sd_type_to_ggml_type(type) == GGML_TYPE_COUNT) {
LOG_ERROR("weight type %s is not supported by this ggml build", sd_type_name(type));
return false;
}
#ifdef SD_USE_UPSTREAM_GGML
for (const auto& rule : split_string(SAFE_STR(tensor_type_rules), ',')) {
const auto pos = rule.find('=');
if (pos != std::string::npos) {
const auto name = rule.substr(pos + 1);
if (name == "f8_e4m3" || name == "f8_e5m2") {
LOG_ERROR("FP8 is not supported by this ggml build (tensor type rule '%s')", rule.c_str());
return false;
}
}
}
#else
GGML_UNUSED(tensor_type_rules);
#endif
return true;
}

KeyValueArgs parse_key_value_args(const char* args, const char* context) {
KeyValueArgs pairs;

Expand Down
1 change: 1 addition & 0 deletions src/core/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ void log_printf(sd_log_level_t level, const char* file, int line, const char* fo
void sd_ggml_log_callback(ggml_log_level level, const char* text, void*);

ggml_type sd_type_to_ggml_type(sd_type_t sdtype);
bool validate_tensor_types(sd_type_t type, const char* tensor_type_rules);

std::string trim(const std::string& s);

Expand Down
4 changes: 4 additions & 0 deletions src/model/common/ggml_block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class Linear : public UnaryBlock {
ggml_tensor* w = params["weight"];
const float scale = ctx->linear_scale > 0.f ? ctx->linear_scale : this->scale;
ggml_tensor* weight_scale = has_weight_scale ? params["weight_scale"] : nullptr;
#ifndef SD_USE_UPSTREAM_GGML
if (w->type == GGML_TYPE_F8_E4M3 || w->type == GGML_TYPE_F8_E5M2) {
bool supports_fp8_matmul = false;
if (ctx->backend != nullptr) {
Expand All @@ -221,6 +222,7 @@ class Linear : public UnaryBlock {
w = ggml_cast(ctx->ggml_ctx, w, GGML_TYPE_BF16);
}
}
#endif
ggml_tensor* b = nullptr;
if (bias) {
b = params["bias"];
Expand All @@ -238,6 +240,7 @@ class Linear : public UnaryBlock {
if (ctx->weight_adapter && b != nullptr) {
b = ctx->weight_adapter->patch_weight(ctx->ggml_ctx, ctx->backend, b, prefix + "bias");
}
#ifndef SD_USE_UPSTREAM_GGML
if (int8_convrot && scale == 1.f) {
const auto cache_key = std::make_pair(x, int8_convrot_group_size);
auto cached = ctx->int8_convrot_cache.find(cache_key);
Expand All @@ -248,6 +251,7 @@ class Linear : public UnaryBlock {
x = cached->second;
}
}
#endif
out = ggml_ext_linear_i8_tensorwise(ctx->ggml_ctx,
x,
w,
Expand Down
12 changes: 12 additions & 0 deletions src/model_io/gguf_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ bool read_gguf_file(const std::string& file_path,

size_t data_offset = gguf_reader.data_offset();
for (const auto& gguf_tensor_info : gguf_reader.tensors()) {
#ifdef SD_USE_UPSTREAM_GGML
if (static_cast<int>(gguf_tensor_info.type) == SD_TYPE_F8_E4M3 ||
static_cast<int>(gguf_tensor_info.type) == SD_TYPE_F8_E5M2) {
set_error(error, "FP8 is not supported by this ggml build (tensor '" + gguf_tensor_info.name + "')");
return false;
}
#endif
if (static_cast<unsigned>(gguf_tensor_info.type) >= GGML_TYPE_COUNT ||
ggml_get_type_traits(gguf_tensor_info.type)->type_size == 0) {
set_error(error, "unsupported GGUF tensor type (tensor '" + gguf_tensor_info.name + "')");
return false;
}
TensorStorage tensor_storage(
gguf_tensor_info.name,
gguf_tensor_info.type,
Expand Down
14 changes: 14 additions & 0 deletions src/model_io/safetensors_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ static ggml_type safetensors_dtype_to_ggml_type(const std::string& dtype) {
ttype = GGML_TYPE_F32;
} else if (dtype == "F64") {
ttype = GGML_TYPE_F32;
#ifndef SD_USE_UPSTREAM_GGML
} else if (dtype == "F8_E4M3") {
ttype = GGML_TYPE_F8_E4M3;
} else if (dtype == "F8_E5M2") {
ttype = GGML_TYPE_F8_E5M2;
#endif
} else if (dtype == "I32") {
ttype = GGML_TYPE_I32;
} else if (dtype == "I64") {
Expand Down Expand Up @@ -230,6 +232,12 @@ bool read_safetensors_file(const std::string& file_path,
if (!read_comfy_quant_config(file, file_path, name, data_start + begin, end - begin, config, error)) {
return false;
}
#ifdef SD_USE_UPSTREAM_GGML
if (config.format == "int8_tensorwise") {
set_error(error, "INT8 tensorwise/convrot is not supported by this ggml build (tensor '" + name + "')");
return false;
}
#endif
const std::string module_name = name.substr(0, name.size() - std::string(".comfy_quant").size());
comfy_quant_configs.emplace(module_name, std::move(config));
}
Expand Down Expand Up @@ -279,6 +287,12 @@ bool read_safetensors_file(const std::string& file_path,
continue;
}

#ifdef SD_USE_UPSTREAM_GGML
if (dtype == "F8_E4M3" || dtype == "F8_E5M2") {
set_error(error, "FP8 is not supported by this ggml build (tensor '" + name + "')");
return false;
}
#endif
ggml_type type = safetensors_dtype_to_ggml_type(dtype);
if (type == GGML_TYPE_COUNT) {
set_error(error, "unsupported dtype '" + dtype + "' (tensor '" + name + "')");
Expand Down
8 changes: 8 additions & 0 deletions src/pipeline/diffusion_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,14 @@ bool StableDiffusionGGML::init_model_loader(ModelLoader& model_loader, ModelConf
}

bool StableDiffusionGGML::init(const sd_ctx_params_t* sd_ctx_params) {
#ifdef SD_USE_UPSTREAM_GGML
LOG_WARN(
"Using upstream GGML: FP8 and INT8 tensorwise/convrot are disabled. "
"Some operators may be unsupported and performance may be lower than with patched GGML.");
#endif
if (!validate_tensor_types(sd_ctx_params->wtype, sd_ctx_params->tensor_type_rules)) {
return false;
}
for (float scale : {sd_ctx_params->linear_scale, sd_ctx_params->attn_scale}) {
if (!std::isfinite(scale) || scale < 0.f || (scale > 0.f && !std::isfinite(1.f / scale))) {
LOG_ERROR("scale overrides must be finite positive values, or 0 to keep model defaults");
Expand Down
17 changes: 15 additions & 2 deletions src/stable-diffusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,26 @@ static float get_cache_reuse_threshold(const sd_cache_params_t& params) {
}

const char* sd_type_name(enum sd_type_t type) {
if ((int)type < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT)) {
return ggml_type_name((ggml_type)type);
if (type == SD_TYPE_F8_E4M3) {
return "f8_e4m3";
}
if (type == SD_TYPE_F8_E5M2) {
return "f8_e5m2";
}
const auto ggml_type = sd_type_to_ggml_type(type);
if (ggml_type != GGML_TYPE_COUNT) {
return ggml_type_name(ggml_type);
}
return NONE_STR;
}

enum sd_type_t str_to_sd_type(const char* str) {
if (!strcmp(str, "f8_e4m3")) {
return SD_TYPE_F8_E4M3;
}
if (!strcmp(str, "f8_e5m2")) {
return SD_TYPE_F8_E5M2;
}
for (int i = 0; i < std::min<int>(SD_TYPE_COUNT, GGML_TYPE_COUNT); i++) {
auto trait = ggml_get_type_traits((ggml_type)i);
if (!strcmp(str, trait->type_name)) {
Expand Down
Loading