Compare commits

...
Author SHA1 Message Date
Wagner Bruna dd753729cc fix: correct double increment on flow denoisers sigma calculations (#1372) 2026-04-08 23:13:05 +08:00
leejet 8afbeb6ba9 chore: normalize text files to utf-8 without bom (#1394) 2026-04-06 21:25:34 +08:00
leejet 5bf438d568 refactor: split examples common into header and source (#1393) 2026-04-06 21:11:57 +08:00
leejet 359eb8b8de refactor: apply RAII ownership to examples (#1392) 2026-04-06 20:33:46 +08:00
leejet 7397ddaa86 feat: add webm support (#1391) 2026-04-06 01:49:28 +08:00
stduhpfandleejet 9369ab759f feat: inpaint improvements (#1357)
* inpaint: get max pixel max instead of single sample

* inpaint: masked diffusion for inpainting models with inflated mask

* refactor tensor interpolate nearest-like reduction paths and generalize max_pool_2d

---------

Co-authored-by: leejet <leejet714@gmail.com>
2026-04-06 00:44:26 +08:00
Wagner Brunaandleejet 687a81f251 chore: make libwebp optional and support system libwebp (#1387)
Co-authored-by: leejet <leejet714@gmail.com>
2026-04-05 23:52:05 +08:00
leejet 87ecb95cbc feat: add webp support (#1384) 2026-04-02 01:36:11 +08:00
Wagner Brunaandleejet 99c1de379b feat: ancestral sampler implementations for flow models (#1374)
* feat: add support for the eta parameter to ancestral samplers

* feat: Euler Ancestral sampler implementation for flow models

* refine flow ancestral sampling and normalize eta defaults

---------

Co-authored-by: leejet <leejet714@gmail.com>
2026-04-02 01:35:29 +08:00
leejet 09b12d5f6d feat(cli): add metadata inspection mode (#1381) 2026-04-01 00:52:03 +08:00
leejet 6dfe945958 fix: use resolved image size in embedded metadata (#1382) 2026-03-31 23:55:49 +08:00
30 changed files with 5499 additions and 2742 deletions
+8 -7
View File
@@ -64,7 +64,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Dependencies
id: depends
@@ -127,7 +127,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Dependencies
id: depends
@@ -205,7 +205,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Get commit hash
id: commit
@@ -239,6 +239,7 @@ jobs:
id: build-push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
file: Dockerfile.${{ matrix.variant }}
@@ -264,7 +265,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Dependencies
id: depends
@@ -345,7 +346,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Install cuda-toolkit
id: cuda-toolkit
@@ -460,7 +461,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Cache ROCm Installation
id: cache-rocm
@@ -573,7 +574,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
version: 10.15.1
- name: Free disk space
run: |
+6
View File
@@ -4,3 +4,9 @@
[submodule "examples/server/frontend"]
path = examples/server/frontend
url = https://github.com/leejet/stable-ui.git
[submodule "thirdparty/libwebp"]
path = thirdparty/libwebp
url = https://github.com/webmproject/libwebp.git
[submodule "thirdparty/libwebm"]
path = thirdparty/libwebm
url = https://github.com/webmproject/libwebm.git
+73
View File
@@ -22,6 +22,26 @@ else()
set(SD_STANDALONE OFF)
endif()
set(SD_SUBMODULE_WEBP FALSE)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/libwebp/CMakeLists.txt")
set(SD_SUBMODULE_WEBP TRUE)
endif()
if(SD_SUBMODULE_WEBP)
set(SD_WEBP_DEFAULT ON)
else()
set(SD_WEBP_DEFAULT ${SD_USE_SYSTEM_WEBP})
endif()
set(SD_SUBMODULE_WEBM FALSE)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/libwebm/CMakeLists.txt")
set(SD_SUBMODULE_WEBM TRUE)
endif()
if(SD_SUBMODULE_WEBM)
set(SD_WEBM_DEFAULT ON)
else()
set(SD_WEBM_DEFAULT ${SD_USE_SYSTEM_WEBM})
endif()
#
# Option list
#
@@ -29,6 +49,10 @@ endif()
# general
#option(SD_BUILD_TESTS "sd: build tests" ${SD_STANDALONE})
option(SD_BUILD_EXAMPLES "sd: build examples" ${SD_STANDALONE})
option(SD_WEBP "sd: enable WebP image I/O support" ${SD_WEBP_DEFAULT})
option(SD_USE_SYSTEM_WEBP "sd: link against system libwebp" OFF)
option(SD_WEBM "sd: enable WebM video output support" ${SD_WEBM_DEFAULT})
option(SD_USE_SYSTEM_WEBM "sd: link against system libwebm" OFF)
option(SD_CUDA "sd: cuda backend" OFF)
option(SD_HIPBLAS "sd: rocm backend" OFF)
option(SD_METAL "sd: metal backend" OFF)
@@ -77,6 +101,55 @@ if(SD_MUSA)
add_definitions(-DSD_USE_CUDA)
endif()
if(SD_WEBP)
if(NOT SD_SUBMODULE_WEBP AND NOT SD_USE_SYSTEM_WEBP)
message(FATAL_ERROR "WebP support enabled but no source found.
Either initialize the submodule:\n git submodule update --init thirdparty/libwebp\n\n"
"Or link against system library:\n cmake (...) -DSD_USE_SYSTEM_WEBP=ON")
endif()
if(SD_USE_SYSTEM_WEBP)
find_package(WebP REQUIRED)
add_library(webp ALIAS WebP::webp)
# libwebp CMake target naming is not consistent across versions/distros.
# Some export WebP::libwebpmux, others export WebP::webpmux.
if(TARGET WebP::libwebpmux)
add_library(libwebpmux ALIAS WebP::libwebpmux)
elseif(TARGET WebP::webpmux)
add_library(libwebpmux ALIAS WebP::webpmux)
else()
message(FATAL_ERROR
"Could not find a compatible webpmux target in system WebP package. "
"Expected WebP::libwebpmux or WebP::webpmux."
)
endif()
endif()
endif()
if(SD_WEBM)
if(NOT SD_WEBP)
message(FATAL_ERROR "SD_WEBM requires SD_WEBP because WebM output reuses libwebp VP8 encoding.")
endif()
if(NOT SD_SUBMODULE_WEBM AND NOT SD_USE_SYSTEM_WEBM)
message(FATAL_ERROR "WebM support enabled but no source found.
Either initialize the submodule:\n git submodule update --init thirdparty/libwebm\n\n"
"Or link against system library:\n cmake (...) -DSD_USE_SYSTEM_WEBM=ON")
endif()
if(SD_USE_SYSTEM_WEBM)
find_path(WEBM_INCLUDE_DIR
NAMES mkvmuxer/mkvmuxer.h mkvparser/mkvparser.h common/webmids.h
PATH_SUFFIXES webm
REQUIRED)
find_library(WEBM_LIBRARY
NAMES webm libwebm
REQUIRED)
add_library(webm UNKNOWN IMPORTED)
set_target_properties(webm PROPERTIES
IMPORTED_LOCATION "${WEBM_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${WEBM_INCLUDE_DIR}")
endif()
endif()
set(SD_LIB stable-diffusion)
file(GLOB SD_LIB_SOURCES
+20
View File
@@ -16,6 +16,26 @@ git submodule init
git submodule update
```
## 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`.
If you do not want WebP/WebM support, you can disable them at configure time:
```shell
mkdir build && cd build
cmake .. -DSD_WEBP=OFF -DSD_WEBM=OFF
cmake --build . --config Release
```
If the submodules are not available, you can also link against system packages instead:
```shell
mkdir build && cd build
cmake .. -DSD_USE_SYSTEM_WEBP=ON -DSD_USE_SYSTEM_WEBM=ON
cmake --build . --config Release
```
## Build (CPU only)
If you don't have a GPU or CUDA installed, you can build a CPU-only version.
+17 -3
View File
@@ -1,6 +1,20 @@
set(TARGET sd-cli)
add_executable(${TARGET} main.cpp)
add_executable(${TARGET}
../common/common.cpp
../common/log.cpp
../common/media_io.cpp
image_metadata.cpp
main.cpp
)
install(TARGETS ${TARGET} RUNTIME)
target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT})
target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17)
target_link_libraries(${TARGET} PRIVATE stable-diffusion zip ${CMAKE_THREAD_LIBS_INIT})
if(SD_WEBP)
target_compile_definitions(${TARGET} PRIVATE SD_USE_WEBP)
target_link_libraries(${TARGET} PRIVATE webp libwebpmux)
endif()
if(SD_WEBM)
target_compile_definitions(${TARGET} PRIVATE SD_USE_WEBM)
target_link_libraries(${TARGET} PRIVATE webm)
endif()
target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17)
+19 -5
View File
@@ -5,18 +5,23 @@ usage: ./bin/sd-cli [options]
CLI Options:
-o, --output <string> path to write result image to. you can use printf-style %d format specifiers for image sequences (default:
./output.png) (eg. output_%03d.png)
--preview-path <string> path to write preview image to (default: ./preview.png)
./output.png) (eg. output_%03d.png). For video generation, single-file outputs support .avi, .webm, and animated .webp
--preview-path <string> path to write preview image to (default: ./preview.png). Multi-frame previews support .avi, .webm, and animated .webp
--preview-interval <int> interval in denoising steps between consecutive updates of the image preview file (default is 1, meaning updating at
every step)
--output-begin-idx <int> starting index for output image sequence, must be non-negative (default 0 if specified %d in output path, 1 otherwise)
--image <string> path to the image to inspect (for metadata mode)
--metadata-format <string> metadata output format, one of [text, json] (default: text)
--canny apply canny preprocessor (edge detection)
--convert-name convert tensor name (for convert mode)
-v, --verbose print extra info
--color colors the logging tags according to level
--taesd-preview-only prevents usage of taesd for decoding the final image. (for use with --preview tae)
--preview-noisy enables previewing noisy inputs of the models rather than the denoised outputs
-M, --mode run mode, one of [img_gen, vid_gen, upscale, convert], default: img_gen
--metadata-raw include raw hex previews for unparsed metadata payloads
--metadata-brief truncate long metadata text values in text output
--metadata-all include structural/container entries such as IHDR, IDAT, and non-metadata JPEG segments
-M, --mode run mode, one of [img_gen, vid_gen, upscale, convert, metadata], default: img_gen
--preview preview method. must be one of the following [none, proj, tae, vae] (default is none)
-h, --help show this help message and exit
@@ -109,7 +114,7 @@ Generation Options:
medium
--skip-layer-start <float> SLG enabling point (default: 0.01)
--skip-layer-end <float> SLG disabling point (default: 0.2)
--eta <float> eta in DDIM, only for DDIM and TCD (default: 0)
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or instruct-pix2pix models (default: same as --cfg-scale)
@@ -117,7 +122,7 @@ Generation Options:
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default: 0)
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
--high-noise-eta <float> (high noise) eta in DDIM, only for DDIM and TCD (default: 0)
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)
--strength <float> strength for noising/unnoising (default: 0.75)
--pm-style-strength <float>
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full destruction of information in init image
@@ -148,3 +153,12 @@ Generation Options:
--scm-mask SCM steps mask for cache-dit: comma-separated 0/1 (e.g., "1,1,1,0,0,1,0,0,1,0") - 1=compute, 0=can cache
--scm-policy SCM policy: 'dynamic' (default) or 'static'
```
Metadata mode inspects PNG/JPEG container metadata without loading any model:
```bash
./bin/sd-cli -M metadata --image ./output.png
./bin/sd-cli -M metadata --image ./output.jpg --metadata-format json
./bin/sd-cli -M metadata --image ./output.png --metadata-raw
./bin/sd-cli -M metadata --image ./output.png --metadata-all
```
-217
View File
@@ -1,217 +0,0 @@
#ifndef __AVI_WRITER_H__
#define __AVI_WRITER_H__
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "stable-diffusion.h"
#ifndef INCLUDE_STB_IMAGE_WRITE_H
#include "stb_image_write.h"
#endif
typedef struct {
uint32_t offset;
uint32_t size;
} avi_index_entry;
// Write 32-bit little-endian integer
void write_u32_le(FILE* f, uint32_t val) {
fwrite(&val, 4, 1, f);
}
// Write 16-bit little-endian integer
void write_u16_le(FILE* f, uint16_t val) {
fwrite(&val, 2, 1, f);
}
/**
* Create an MJPG AVI file from an array of sd_image_t images.
* Images are encoded to JPEG using stb_image_write.
*
* @param filename Output AVI file name.
* @param images Array of input images.
* @param num_images Number of images in the array.
* @param fps Frames per second for the video.
* @param quality JPEG quality (0-100).
* @return 0 on success, -1 on failure.
*/
int create_mjpg_avi_from_sd_images(const char* filename, sd_image_t* images, int num_images, int fps, int quality = 90) {
if (num_images == 0) {
fprintf(stderr, "Error: Image array is empty.\n");
return -1;
}
FILE* f = fopen(filename, "wb");
if (!f) {
perror("Error opening file for writing");
return -1;
}
uint32_t width = images[0].width;
uint32_t height = images[0].height;
uint32_t channels = images[0].channel;
if (channels != 3 && channels != 4) {
fprintf(stderr, "Error: Unsupported channel count: %u\n", channels);
fclose(f);
return -1;
}
// --- RIFF AVI Header ---
fwrite("RIFF", 4, 1, f);
long riff_size_pos = ftell(f);
write_u32_le(f, 0); // Placeholder for file size
fwrite("AVI ", 4, 1, f);
// 'hdrl' LIST (header list)
fwrite("LIST", 4, 1, f);
write_u32_le(f, 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40);
fwrite("hdrl", 4, 1, f);
// 'avih' chunk (AVI main header)
fwrite("avih", 4, 1, f);
write_u32_le(f, 56);
write_u32_le(f, 1000000 / fps); // Microseconds per frame
write_u32_le(f, 0); // Max bytes per second
write_u32_le(f, 0); // Padding granularity
write_u32_le(f, 0x110); // Flags (HASINDEX | ISINTERLEAVED)
write_u32_le(f, num_images); // Total frames
write_u32_le(f, 0); // Initial frames
write_u32_le(f, 1); // Number of streams
write_u32_le(f, width * height * 3); // Suggested buffer size
write_u32_le(f, width);
write_u32_le(f, height);
write_u32_le(f, 0); // Reserved
write_u32_le(f, 0); // Reserved
write_u32_le(f, 0); // Reserved
write_u32_le(f, 0); // Reserved
// 'strl' LIST (stream list)
fwrite("LIST", 4, 1, f);
write_u32_le(f, 4 + 8 + 56 + 8 + 40);
fwrite("strl", 4, 1, f);
// 'strh' chunk (stream header)
fwrite("strh", 4, 1, f);
write_u32_le(f, 56);
fwrite("vids", 4, 1, f); // Stream type: video
fwrite("MJPG", 4, 1, f); // Codec: Motion JPEG
write_u32_le(f, 0); // Flags
write_u16_le(f, 0); // Priority
write_u16_le(f, 0); // Language
write_u32_le(f, 0); // Initial frames
write_u32_le(f, 1); // Scale
write_u32_le(f, fps); // Rate
write_u32_le(f, 0); // Start
write_u32_le(f, num_images); // Length
write_u32_le(f, width * height * 3); // Suggested buffer size
write_u32_le(f, (uint32_t)-1); // Quality
write_u32_le(f, 0); // Sample size
write_u16_le(f, 0); // rcFrame.left
write_u16_le(f, 0); // rcFrame.top
write_u16_le(f, 0); // rcFrame.right
write_u16_le(f, 0); // rcFrame.bottom
// 'strf' chunk (stream format: BITMAPINFOHEADER)
fwrite("strf", 4, 1, f);
write_u32_le(f, 40);
write_u32_le(f, 40); // biSize
write_u32_le(f, width);
write_u32_le(f, height);
write_u16_le(f, 1); // biPlanes
write_u16_le(f, 24); // biBitCount
fwrite("MJPG", 4, 1, f); // biCompression (FOURCC)
write_u32_le(f, width * height * 3); // biSizeImage
write_u32_le(f, 0); // XPelsPerMeter
write_u32_le(f, 0); // YPelsPerMeter
write_u32_le(f, 0); // Colors used
write_u32_le(f, 0); // Colors important
// 'movi' LIST (video frames)
// long movi_list_pos = ftell(f);
fwrite("LIST", 4, 1, f);
long movi_size_pos = ftell(f);
write_u32_le(f, 0); // Placeholder for movi size
fwrite("movi", 4, 1, f);
avi_index_entry* index = (avi_index_entry*)malloc(sizeof(avi_index_entry) * num_images);
if (!index) {
fclose(f);
return -1;
}
// Encode and write each frame as JPEG
struct {
uint8_t* buf;
size_t size;
} jpeg_data;
for (int i = 0; i < num_images; i++) {
jpeg_data.buf = nullptr;
jpeg_data.size = 0;
// Callback function to collect JPEG data into memory
auto write_to_buf = [](void* context, void* data, int size) {
auto jd = (decltype(jpeg_data)*)context;
jd->buf = (uint8_t*)realloc(jd->buf, jd->size + size);
memcpy(jd->buf + jd->size, data, size);
jd->size += size;
};
// Encode to JPEG in memory
stbi_write_jpg_to_func(
write_to_buf,
&jpeg_data,
images[i].width,
images[i].height,
channels,
images[i].data,
quality);
// Write '00dc' chunk (video frame)
fwrite("00dc", 4, 1, f);
write_u32_le(f, (uint32_t)jpeg_data.size);
index[i].offset = ftell(f) - 8;
index[i].size = (uint32_t)jpeg_data.size;
fwrite(jpeg_data.buf, 1, jpeg_data.size, f);
// Align to even byte size
if (jpeg_data.size % 2)
fputc(0, f);
free(jpeg_data.buf);
}
// Finalize 'movi' size
long cur_pos = ftell(f);
long movi_size = cur_pos - movi_size_pos - 4;
fseek(f, movi_size_pos, SEEK_SET);
write_u32_le(f, movi_size);
fseek(f, cur_pos, SEEK_SET);
// Write 'idx1' index
fwrite("idx1", 4, 1, f);
write_u32_le(f, num_images * 16);
for (int i = 0; i < num_images; i++) {
fwrite("00dc", 4, 1, f);
write_u32_le(f, 0x10);
write_u32_le(f, index[i].offset);
write_u32_le(f, index[i].size);
}
// Finalize RIFF size
cur_pos = ftell(f);
long file_size = cur_pos - riff_size_pos - 4;
fseek(f, riff_size_pos, SEEK_SET);
write_u32_le(f, file_size);
fseek(f, cur_pos, SEEK_SET);
fclose(f);
free(index);
return 0;
}
#endif // __AVI_WRITER_H__
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <iosfwd>
#include <string>
enum class MetadataOutputFormat {
TEXT,
JSON,
};
struct MetadataReadOptions {
MetadataOutputFormat output_format = MetadataOutputFormat::TEXT;
bool include_raw = false;
bool brief = false;
bool include_structural = false;
};
bool print_image_metadata(const std::string& image_path,
const MetadataReadOptions& options,
std::ostream& out,
std::string& error);
+156 -129
View File
@@ -15,9 +15,12 @@
// #include "preprocessing.hpp"
#include "stable-diffusion.h"
#include "common/common.hpp"
#include "common/common.h"
#include "common/media_io.h"
#include "common/resource_owners.hpp"
#include "image_metadata.h"
#include "avi_writer.h"
namespace fs = std::filesystem;
const char* previews_str[] = {
"none",
@@ -32,6 +35,8 @@ struct SDCliParams {
SDMode mode = IMG_GEN;
std::string output_path = "output.png";
int output_begin_idx = -1;
std::string image_path;
std::string metadata_format = "text";
bool verbose = false;
bool canny_preprocess = false;
@@ -44,6 +49,9 @@ struct SDCliParams {
bool taesd_preview = false;
bool preview_noisy = false;
bool color = false;
bool metadata_raw = false;
bool metadata_brief = false;
bool metadata_all = false;
bool normal_exit = false;
@@ -53,11 +61,19 @@ struct SDCliParams {
options.string_options = {
{"-o",
"--output",
"path to write result image to. you can use printf-style %d format specifiers for image sequences (default: ./output.png) (eg. output_%03d.png)",
"path to write result image to. you can use printf-style %d format specifiers for image sequences (default: ./output.png) (eg. output_%03d.png). Single-file video outputs support .avi, .webm, and animated .webp",
&output_path},
{"",
"--image",
"path to the image to inspect (for metadata mode)",
&image_path},
{"",
"--metadata-format",
"metadata output format, one of [text, json] (default: text)",
&metadata_format},
{"",
"--preview-path",
"path to write preview image to (default: ./preview.png)",
"path to write preview image to (default: ./preview.png). Multi-frame previews support .avi, .webm, and animated .webp",
&preview_path},
};
@@ -97,6 +113,18 @@ struct SDCliParams {
"--preview-noisy",
"enables previewing noisy inputs of the models rather than the denoised outputs",
true, &preview_noisy},
{"",
"--metadata-raw",
"include raw hex previews for unparsed metadata payloads",
true, &metadata_raw},
{"",
"--metadata-brief",
"truncate long metadata text values in text output",
true, &metadata_brief},
{"",
"--metadata-all",
"include structural/container entries such as IHDR, IDAT, and non-metadata JPEG segments",
true, &metadata_all},
};
@@ -149,7 +177,7 @@ struct SDCliParams {
options.manual_options = {
{"-M",
"--mode",
"run mode, one of [img_gen, vid_gen, upscale, convert], default: img_gen",
"run mode, one of [img_gen, vid_gen, upscale, convert, metadata], default: img_gen",
on_mode_arg},
{"",
"--preview",
@@ -165,7 +193,7 @@ struct SDCliParams {
};
bool process_and_check() {
if (output_path.length() == 0) {
if (mode != METADATA && output_path.length() == 0) {
LOG_ERROR("error: the following arguments are required: output_path");
return false;
}
@@ -174,6 +202,16 @@ struct SDCliParams {
if (output_path == "output.png") {
output_path = "output.gguf";
}
} else if (mode == METADATA) {
if (image_path.empty()) {
LOG_ERROR("error: metadata mode needs an image path (--image)");
return false;
}
if (metadata_format != "text" && metadata_format != "json") {
LOG_ERROR("error: invalid metadata format %s, must be one of [text, json]",
metadata_format.c_str());
return false;
}
}
return true;
}
@@ -183,6 +221,8 @@ struct SDCliParams {
oss << "SDCliParams {\n"
<< " mode: " << modes_str[mode] << ",\n"
<< " output_path: \"" << output_path << "\",\n"
<< " image_path: \"" << image_path << "\",\n"
<< " metadata_format: \"" << metadata_format << "\",\n"
<< " verbose: " << (verbose ? "true" : "false") << ",\n"
<< " color: " << (color ? "true" : "false") << ",\n"
<< " canny_preprocess: " << (canny_preprocess ? "true" : "false") << ",\n"
@@ -192,7 +232,10 @@ struct SDCliParams {
<< " preview_path: \"" << preview_path << "\",\n"
<< " preview_fps: " << preview_fps << ",\n"
<< " taesd_preview: " << (taesd_preview ? "true" : "false") << ",\n"
<< " preview_noisy: " << (preview_noisy ? "true" : "false") << "\n"
<< " preview_noisy: " << (preview_noisy ? "true" : "false") << ",\n"
<< " metadata_raw: " << (metadata_raw ? "true" : "false") << ",\n"
<< " metadata_brief: " << (metadata_brief ? "true" : "false") << ",\n"
<< " metadata_all: " << (metadata_all ? "true" : "false") << "\n"
<< "}";
return oss.str();
}
@@ -217,9 +260,13 @@ void parse_args(int argc, const char** argv, SDCliParams& cli_params, SDContextP
exit(cli_params.normal_exit ? 0 : 1);
}
if (!cli_params.process_and_check() ||
!ctx_params.process_and_check(cli_params.mode) ||
!gen_params.process_and_check(cli_params.mode, ctx_params.lora_model_dir)) {
bool valid = cli_params.process_and_check();
if (valid && cli_params.mode != METADATA) {
valid = ctx_params.process_and_check(cli_params.mode) &&
gen_params.process_and_check(cli_params.mode, ctx_params.lora_model_dir);
}
if (!valid) {
print_usage(argc, argv, options_vec);
exit(1);
}
@@ -231,7 +278,7 @@ void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) {
}
bool load_images_from_dir(const std::string dir,
std::vector<sd_image_t>& images,
SDImageVec& images,
int expected_width = 0,
int expected_height = 0,
int max_image_num = 0,
@@ -258,7 +305,7 @@ bool load_images_from_dir(const std::string dir,
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp") {
if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".webp") {
LOG_DEBUG("load image %zu from '%s'", images.size(), path.c_str());
int width = 0;
int height = 0;
@@ -273,7 +320,7 @@ bool load_images_from_dir(const std::string dir,
3,
image_buffer});
if (max_image_num > 0 && images.size() >= max_image_num) {
if (max_image_num > 0 && static_cast<int>(images.size()) >= max_image_num) {
break;
}
}
@@ -288,9 +335,17 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
// is_noisy is set to true if the preview corresponds to noisy latents, false if it's denoised latents
// unused in this app, it will either be always noisy or always denoised here
if (frame_count == 1) {
stbi_write_png(cli_params->preview_path.c_str(), image->width, image->height, image->channel, image->data, 0);
if (!write_image_to_file(cli_params->preview_path,
image->data,
image->width,
image->height,
image->channel)) {
LOG_ERROR("save preview image to '%s' failed", cli_params->preview_path.c_str());
}
} else {
create_mjpg_avi_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, cli_params->preview_fps);
if (create_video_from_sd_images(cli_params->preview_path.c_str(), image, frame_count, cli_params->preview_fps) != 0) {
LOG_ERROR("save preview video to '%s' failed", cli_params->preview_path.c_str());
}
}
}
@@ -340,9 +395,13 @@ bool save_results(const SDCliParams& cli_params,
std::string ext_lower = ext.string();
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
bool is_jpg = (ext_lower == ".jpg" || ext_lower == ".jpeg" || ext_lower == ".jpe");
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
if (!ext.empty()) {
if (is_jpg || ext_lower == ".png") {
if (output_format == EncodedImageFormat::JPEG ||
output_format == EncodedImageFormat::PNG ||
output_format == EncodedImageFormat::WEBP ||
ext_lower == ".avi" ||
ext_lower == ".webm") {
base_path.replace_extension();
}
}
@@ -360,20 +419,15 @@ bool save_results(const SDCliParams& cli_params,
std::string params = gen_params.embed_image_metadata
? get_image_params(ctx_params, gen_params, gen_params.seed + idx)
: "";
int ok = 0;
if (is_jpg) {
ok = stbi_write_jpg(path.string().c_str(), img.width, img.height, img.channel, img.data, 90, params.size() > 0 ? params.c_str() : nullptr);
} else {
ok = stbi_write_png(path.string().c_str(), img.width, img.height, img.channel, img.data, 0, params.size() > 0 ? params.c_str() : nullptr);
}
const bool ok = write_image_to_file(path.string(), img.data, img.width, img.height, img.channel, params, 90);
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
return ok != 0;
return ok;
};
int sucessful_reults = 0;
if (std::regex_search(cli_params.output_path, format_specifier_regex)) {
if (!is_jpg && ext_lower != ".png")
if (output_format == EncodedImageFormat::UNKNOWN)
ext = ".png";
fs::path pattern = base_path;
pattern += ext;
@@ -389,20 +443,20 @@ bool save_results(const SDCliParams& cli_params,
}
if (cli_params.mode == VID_GEN && num_results > 1) {
if (ext_lower != ".avi")
if (ext_lower != ".avi" && ext_lower != ".webp" && ext_lower != ".webm")
ext = ".avi";
fs::path video_path = base_path;
video_path += ext;
if (create_mjpg_avi_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps) == 0) {
LOG_INFO("save result MJPG AVI video to '%s'", video_path.string().c_str());
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps) == 0) {
LOG_INFO("save result video to '%s'", video_path.string().c_str());
return true;
} else {
LOG_ERROR("Failed to save result MPG AVI video to '%s'", video_path.string().c_str());
LOG_ERROR("Failed to save result video to '%s'", video_path.string().c_str());
return false;
}
}
if (!is_jpg && ext_lower != ".png")
if (output_format == EncodedImageFormat::UNKNOWN)
ext = ".png";
for (int i = 0; i < num_results; ++i) {
@@ -430,6 +484,27 @@ int main(int argc, const char* argv[]) {
SDGenerationParams gen_params;
parse_args(argc, argv, cli_params, ctx_params, gen_params);
sd_set_log_callback(sd_log_cb, (void*)&cli_params);
log_verbose = cli_params.verbose;
log_color = cli_params.color;
if (cli_params.mode == METADATA) {
MetadataReadOptions options;
options.output_format = cli_params.metadata_format == "json"
? MetadataOutputFormat::JSON
: MetadataOutputFormat::TEXT;
options.include_raw = cli_params.metadata_raw;
options.brief = cli_params.metadata_brief;
options.include_structural = cli_params.metadata_all;
std::string error;
if (!print_image_metadata(cli_params.image_path, options, std::cout, error)) {
LOG_ERROR("%s", error.c_str());
return 1;
}
return 0;
}
if (gen_params.video_frames > 4) {
size_t last_dot_pos = cli_params.preview_path.find_last_of(".");
std::string base_path = cli_params.preview_path;
@@ -447,9 +522,6 @@ int main(int argc, const char* argv[]) {
if (cli_params.preview_method == PREVIEW_PROJ)
cli_params.preview_fps /= 4;
sd_set_log_callback(sd_log_cb, (void*)&cli_params);
log_verbose = cli_params.verbose;
log_color = cli_params.color;
sd_set_preview_callback(step_callback,
cli_params.preview_method,
cli_params.preview_interval,
@@ -485,39 +557,17 @@ int main(int argc, const char* argv[]) {
}
}
bool vae_decode_only = true;
sd_image_t init_image = {0, 0, 3, nullptr};
sd_image_t end_image = {0, 0, 3, nullptr};
sd_image_t control_image = {0, 0, 3, nullptr};
sd_image_t mask_image = {0, 0, 1, nullptr};
std::vector<sd_image_t> ref_images;
std::vector<sd_image_t> pmid_images;
std::vector<sd_image_t> control_frames;
auto release_all_resources = [&]() {
free(init_image.data);
free(end_image.data);
free(control_image.data);
free(mask_image.data);
for (auto image : ref_images) {
free(image.data);
image.data = nullptr;
}
ref_images.clear();
for (auto image : pmid_images) {
free(image.data);
image.data = nullptr;
}
pmid_images.clear();
for (auto image : control_frames) {
free(image.data);
image.data = nullptr;
}
control_frames.clear();
};
bool vae_decode_only = true;
SDImageOwner init_image({0, 0, 3, nullptr});
SDImageOwner end_image({0, 0, 3, nullptr});
SDImageOwner control_image({0, 0, 3, nullptr});
SDImageOwner mask_image({0, 0, 1, nullptr});
SDImageVec ref_images;
SDImageVec pmid_images;
SDImageVec control_frames;
auto load_image_and_update_size = [&](const std::string& path,
sd_image_t& image,
SDImageOwner& image,
bool resize_image = true,
int expected_channel = 3) -> bool {
int expected_width = 0;
@@ -527,13 +577,12 @@ int main(int argc, const char* argv[]) {
expected_height = gen_params.height;
}
if (!load_sd_image_from_file(&image, path.c_str(), expected_width, expected_height, expected_channel)) {
if (!load_sd_image_from_file(image.put(), path.c_str(), expected_width, expected_height, expected_channel)) {
LOG_ERROR("load image from '%s' failed", path.c_str());
release_all_resources();
return false;
}
gen_params.set_width_and_height_if_unset(image.width, image.height);
gen_params.set_width_and_height_if_unset(image.get().width, image.get().height);
return true;
};
@@ -554,47 +603,46 @@ int main(int argc, const char* argv[]) {
if (gen_params.ref_image_paths.size() > 0) {
vae_decode_only = false;
for (auto& path : gen_params.ref_image_paths) {
sd_image_t ref_image = {0, 0, 3, nullptr};
SDImageOwner ref_image({0, 0, 3, nullptr});
if (!load_image_and_update_size(path, ref_image, false)) {
return 1;
}
ref_images.push_back(ref_image);
ref_images.push_back(std::move(ref_image));
}
}
if (gen_params.mask_image_path.size() > 0) {
if (!load_sd_image_from_file(&mask_image,
if (!load_sd_image_from_file(mask_image.put(),
gen_params.mask_image_path.c_str(),
gen_params.get_resolved_width(),
gen_params.get_resolved_height(),
1)) {
LOG_ERROR("load image from '%s' failed", gen_params.mask_image_path.c_str());
release_all_resources();
return 1;
}
} else {
mask_image.data = (uint8_t*)malloc(gen_params.get_resolved_width() * gen_params.get_resolved_height());
if (mask_image.data == nullptr) {
sd_image_t generated_mask = {0, 0, 1, nullptr};
generated_mask.data = (uint8_t*)malloc(gen_params.get_resolved_width() * gen_params.get_resolved_height());
if (generated_mask.data == nullptr) {
LOG_ERROR("malloc mask image failed");
release_all_resources();
return 1;
}
mask_image.width = gen_params.get_resolved_width();
mask_image.height = gen_params.get_resolved_height();
memset(mask_image.data, 255, gen_params.get_resolved_width() * gen_params.get_resolved_height());
generated_mask.width = gen_params.get_resolved_width();
generated_mask.height = gen_params.get_resolved_height();
memset(generated_mask.data, 255, gen_params.get_resolved_width() * gen_params.get_resolved_height());
mask_image.reset(generated_mask);
}
if (gen_params.control_image_path.size() > 0) {
if (!load_sd_image_from_file(&control_image,
if (!load_sd_image_from_file(control_image.put(),
gen_params.control_image_path.c_str(),
gen_params.get_resolved_width(),
gen_params.get_resolved_height())) {
LOG_ERROR("load image from '%s' failed", gen_params.control_image_path.c_str());
release_all_resources();
return 1;
}
if (cli_params.canny_preprocess) { // apply preprocessor
preprocess_canny(control_image,
preprocess_canny(control_image.get(),
0.08f,
0.08f,
0.8f,
@@ -610,7 +658,6 @@ int main(int argc, const char* argv[]) {
gen_params.get_resolved_height(),
gen_params.video_frames,
cli_params.verbose)) {
release_all_resources();
return 1;
}
}
@@ -622,7 +669,6 @@ int main(int argc, const char* argv[]) {
0,
0,
cli_params.verbose)) {
release_all_resources();
return 1;
}
}
@@ -633,39 +679,30 @@ int main(int argc, const char* argv[]) {
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(vae_decode_only, true, cli_params.taesd_preview);
sd_image_t* results = nullptr;
int num_results = 0;
SDImageVec results;
int num_results = 0;
if (cli_params.mode == UPSCALE) {
num_results = 1;
results = (sd_image_t*)calloc(num_results, sizeof(sd_image_t));
if (results == nullptr) {
LOG_INFO("failed to allocate results array");
release_all_resources();
return 1;
}
results[0] = init_image;
init_image.data = nullptr;
results.push_back(init_image.release());
} else {
sd_ctx_t* sd_ctx = new_sd_ctx(&sd_ctx_params);
SDCtxPtr sd_ctx(new_sd_ctx(&sd_ctx_params));
if (sd_ctx == nullptr) {
LOG_INFO("new_sd_ctx_t failed");
release_all_resources();
return 1;
}
if (gen_params.sample_params.sample_method == SAMPLE_METHOD_COUNT) {
gen_params.sample_params.sample_method = sd_get_default_sample_method(sd_ctx);
gen_params.sample_params.sample_method = sd_get_default_sample_method(sd_ctx.get());
}
if (gen_params.high_noise_sample_params.sample_method == SAMPLE_METHOD_COUNT) {
gen_params.high_noise_sample_params.sample_method = sd_get_default_sample_method(sd_ctx);
gen_params.high_noise_sample_params.sample_method = sd_get_default_sample_method(sd_ctx.get());
}
if (gen_params.sample_params.scheduler == SCHEDULER_COUNT) {
gen_params.sample_params.scheduler = sd_get_default_scheduler(sd_ctx, gen_params.sample_params.sample_method);
gen_params.sample_params.scheduler = sd_get_default_scheduler(sd_ctx.get(), gen_params.sample_params.sample_method);
}
if (cli_params.mode == IMG_GEN) {
@@ -675,19 +712,19 @@ int main(int argc, const char* argv[]) {
gen_params.prompt.c_str(),
gen_params.negative_prompt.c_str(),
gen_params.clip_skip,
init_image,
init_image.get(),
ref_images.data(),
(int)ref_images.size(),
gen_params.auto_resize_ref_image,
gen_params.increase_ref_index,
mask_image,
mask_image.get(),
gen_params.get_resolved_width(),
gen_params.get_resolved_height(),
gen_params.sample_params,
gen_params.strength,
gen_params.seed,
gen_params.batch_count,
control_image,
control_image.get(),
gen_params.control_strength,
{
pmid_images.data(),
@@ -699,8 +736,8 @@ int main(int argc, const char* argv[]) {
gen_params.cache_params,
};
results = generate_image(sd_ctx, &img_gen_params);
num_results = gen_params.batch_count;
results.adopt(generate_image(sd_ctx.get(), &img_gen_params), num_results);
} else if (cli_params.mode == VID_GEN) {
sd_vid_gen_params_t vid_gen_params = {
gen_params.lora_vec.data(),
@@ -708,8 +745,8 @@ int main(int argc, const char* argv[]) {
gen_params.prompt.c_str(),
gen_params.negative_prompt.c_str(),
gen_params.clip_skip,
init_image,
end_image,
init_image.get(),
end_image.get(),
control_frames.data(),
(int)control_frames.size(),
gen_params.get_resolved_width(),
@@ -725,25 +762,23 @@ int main(int argc, const char* argv[]) {
gen_params.cache_params,
};
results = generate_video(sd_ctx, &vid_gen_params, &num_results);
sd_image_t* generated_video = generate_video(sd_ctx.get(), &vid_gen_params, &num_results);
results.adopt(generated_video, num_results);
}
if (results == nullptr) {
if (!results) {
LOG_ERROR("generate failed");
free_sd_ctx(sd_ctx);
return 1;
}
free_sd_ctx(sd_ctx);
}
int upscale_factor = 4; // unused for RealESRGAN_x4plus_anime_6B.pth
if (ctx_params.esrgan_path.size() > 0 && gen_params.upscale_repeats > 0) {
upscaler_ctx_t* upscaler_ctx = new_upscaler_ctx(ctx_params.esrgan_path.c_str(),
ctx_params.offload_params_to_cpu,
ctx_params.diffusion_conv_direct,
ctx_params.n_threads,
gen_params.upscale_tile_size);
UpscalerCtxPtr upscaler_ctx(new_upscaler_ctx(ctx_params.esrgan_path.c_str(),
ctx_params.offload_params_to_cpu,
ctx_params.diffusion_conv_direct,
ctx_params.n_threads,
gen_params.upscale_tile_size));
if (upscaler_ctx == nullptr) {
LOG_ERROR("new_upscaler_ctx failed");
@@ -752,32 +787,24 @@ int main(int argc, const char* argv[]) {
if (results[i].data == nullptr) {
continue;
}
sd_image_t current_image = results[i];
SDImageOwner current_image(results[i]);
results[i] = {0, 0, 0, nullptr};
for (int u = 0; u < gen_params.upscale_repeats; ++u) {
sd_image_t upscaled_image = upscale(upscaler_ctx, current_image, upscale_factor);
if (upscaled_image.data == nullptr) {
SDImageOwner upscaled_image(upscale(upscaler_ctx.get(), current_image.get(), upscale_factor));
if (upscaled_image.get().data == nullptr) {
LOG_ERROR("upscale failed");
break;
}
free(current_image.data);
current_image = upscaled_image;
current_image = std::move(upscaled_image);
}
results[i] = current_image; // Set the final upscaled image as the result
results[i] = current_image.release(); // Set the final upscaled image as the result
}
}
}
if (!save_results(cli_params, ctx_params, gen_params, results, num_results)) {
if (!save_results(cli_params, ctx_params, gen_params, results.data(), num_results)) {
return 1;
}
for (int i = 0; i < num_results; i++) {
free(results[i].data);
results[i].data = nullptr;
}
free(results);
release_all_resources();
return 0;
}
File diff suppressed because it is too large Load Diff
+207
View File
@@ -0,0 +1,207 @@
#ifndef __EXAMPLES_COMMON_COMMON_H__
#define __EXAMPLES_COMMON_COMMON_H__
#include <cmath>
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <vector>
#include "log.h"
#include "stable-diffusion.h"
#define SAFE_STR(s) ((s) ? (s) : "")
#define BOOL_STR(b) ((b) ? "true" : "false")
extern const char* const modes_str[];
#define SD_ALL_MODES_STR "img_gen, vid_gen, convert, upscale, metadata"
enum SDMode {
IMG_GEN,
VID_GEN,
CONVERT,
UPSCALE,
METADATA,
MODE_COUNT
};
struct StringOption {
std::string short_name;
std::string long_name;
std::string desc;
std::string* target;
};
struct IntOption {
std::string short_name;
std::string long_name;
std::string desc;
int* target;
};
struct FloatOption {
std::string short_name;
std::string long_name;
std::string desc;
float* target;
};
struct BoolOption {
std::string short_name;
std::string long_name;
std::string desc;
bool keep_true;
bool* target;
};
struct ManualOption {
std::string short_name;
std::string long_name;
std::string desc;
std::function<int(int argc, const char** argv, int index)> cb;
};
struct ArgOptions {
std::vector<StringOption> string_options;
std::vector<IntOption> int_options;
std::vector<FloatOption> float_options;
std::vector<BoolOption> bool_options;
std::vector<ManualOption> manual_options;
static std::string wrap_text(const std::string& text, size_t width, size_t indent);
void print() const;
};
bool parse_options(int argc, const char** argv, const std::vector<ArgOptions>& options_list);
struct SDContextParams {
int n_threads = -1;
std::string model_path;
std::string clip_l_path;
std::string clip_g_path;
std::string clip_vision_path;
std::string t5xxl_path;
std::string llm_path;
std::string llm_vision_path;
std::string diffusion_model_path;
std::string high_noise_diffusion_model_path;
std::string vae_path;
std::string taesd_path;
std::string esrgan_path;
std::string control_net_path;
std::string embedding_dir;
std::string photo_maker_path;
sd_type_t wtype = SD_TYPE_COUNT;
std::string tensor_type_rules;
std::string lora_model_dir = ".";
std::map<std::string, std::string> embedding_map;
std::vector<sd_embedding_t> embedding_vec;
rng_type_t rng_type = CUDA_RNG;
rng_type_t sampler_rng_type = RNG_TYPE_COUNT;
bool offload_params_to_cpu = false;
bool enable_mmap = false;
bool control_net_cpu = false;
bool clip_on_cpu = false;
bool vae_on_cpu = false;
bool flash_attn = false;
bool diffusion_flash_attn = false;
bool diffusion_conv_direct = false;
bool vae_conv_direct = false;
bool circular = false;
bool circular_x = false;
bool circular_y = false;
bool chroma_use_dit_mask = true;
bool chroma_use_t5_mask = false;
int chroma_t5_mask_pad = 1;
bool qwen_image_zero_cond_t = false;
prediction_t prediction = PREDICTION_COUNT;
lora_apply_mode_t lora_apply_mode = LORA_APPLY_AUTO;
bool force_sdxl_vae_conv_scale = false;
float flow_shift = INFINITY;
ArgOptions get_options();
void build_embedding_map();
bool process_and_check(SDMode mode);
std::string to_string() const;
sd_ctx_params_t to_sd_ctx_params_t(bool vae_decode_only, bool free_params_immediately, bool taesd_preview);
};
struct SDGenerationParams {
std::string prompt;
std::string prompt_with_lora; // for metadata record only
std::string negative_prompt;
int clip_skip = -1; // <= 0 represents unspecified
int width = -1;
int height = -1;
int batch_count = 1;
std::string init_image_path;
std::string end_image_path;
std::string mask_image_path;
std::string control_image_path;
std::vector<std::string> ref_image_paths;
std::string control_video_path;
bool auto_resize_ref_image = true;
bool increase_ref_index = false;
bool embed_image_metadata = true;
std::vector<int> skip_layers = {7, 8, 9};
sd_sample_params_t sample_params;
std::vector<int> high_noise_skip_layers = {7, 8, 9};
sd_sample_params_t high_noise_sample_params;
std::vector<float> custom_sigmas;
std::string cache_mode;
std::string cache_option;
std::string scm_mask;
bool scm_policy_dynamic = true;
sd_cache_params_t cache_params{};
float moe_boundary = 0.875f;
int video_frames = 1;
int fps = 16;
float vace_strength = 1.f;
float strength = 0.75f;
float control_strength = 0.9f;
int64_t seed = 42;
sd_tiling_params_t vae_tiling_params = {false, 0, 0, 0.5f, 0.0f, 0.0f};
// Photo Maker
std::string pm_id_images_dir;
std::string pm_id_embed_path;
float pm_style_strength = 20.f;
int upscale_repeats = 1;
int upscale_tile_size = 128;
std::map<std::string, float> lora_map;
std::map<std::string, float> high_noise_lora_map;
std::vector<sd_lora_t> lora_vec;
SDGenerationParams();
ArgOptions get_options();
bool from_json_str(const std::string& json_str);
void extract_and_remove_lora(const std::string& lora_model_dir);
bool width_and_height_are_set() const;
void set_width_and_height_if_unset(int w, int h);
int get_resolved_width() const;
int get_resolved_height() const;
bool process_and_check(SDMode mode, const std::string& lora_model_dir);
std::string to_string() const;
};
std::string version_string();
std::string get_image_params(const SDContextParams& ctx_params, const SDGenerationParams& gen_params, int64_t seed);
#endif // __EXAMPLES_COMMON_COMMON_H__
File diff suppressed because it is too large Load Diff
+115
View File
@@ -0,0 +1,115 @@
#include "log.h"
#include <vector>
bool log_verbose = false;
bool log_color = false;
std::string sd_basename(const std::string& path) {
size_t pos = path.find_last_of('/');
if (pos != std::string::npos) {
return path.substr(pos + 1);
}
pos = path.find_last_of('\\');
if (pos != std::string::npos) {
return path.substr(pos + 1);
}
return path;
}
void print_utf8(FILE* stream, const char* utf8) {
if (!utf8) {
return;
}
#ifdef _WIN32
HANDLE h = (stream == stderr)
? GetStdHandle(STD_ERROR_HANDLE)
: GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode;
BOOL is_console = GetConsoleMode(h, &mode);
if (is_console) {
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (wlen <= 0) {
return;
}
std::vector<wchar_t> wbuf(static_cast<size_t>(wlen));
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wbuf.data(), wlen);
DWORD written;
WriteConsoleW(h, wbuf.data(), wlen - 1, &written, NULL);
} else {
DWORD written;
WriteFile(h, utf8, (DWORD)strlen(utf8), &written, NULL);
}
#else
fputs(utf8, stream);
#endif
}
void log_print(enum sd_log_level_t level, const char* log, bool verbose, bool color) {
int tag_color;
const char* level_str;
FILE* out_stream = (level == SD_LOG_ERROR) ? stderr : stdout;
if (!log || (!verbose && level <= SD_LOG_DEBUG)) {
return;
}
switch (level) {
case SD_LOG_DEBUG:
tag_color = 37;
level_str = "DEBUG";
break;
case SD_LOG_INFO:
tag_color = 34;
level_str = "INFO";
break;
case SD_LOG_WARN:
tag_color = 35;
level_str = "WARN";
break;
case SD_LOG_ERROR:
tag_color = 31;
level_str = "ERROR";
break;
default:
tag_color = 33;
level_str = "?????";
break;
}
if (color) {
fprintf(out_stream, "\033[%d;1m[%-5s]\033[0m ", tag_color, level_str);
} else {
fprintf(out_stream, "[%-5s] ", level_str);
}
print_utf8(out_stream, log);
fflush(out_stream);
}
void example_log_printf(sd_log_level_t level, const char* file, int line, const char* format, ...) {
constexpr size_t LOG_BUFFER_SIZE = 4096;
va_list args;
va_start(args, format);
static char log_buffer[LOG_BUFFER_SIZE + 1];
int written = snprintf(log_buffer, LOG_BUFFER_SIZE, "%s:%-4d - ", sd_basename(file).c_str(), line);
if (written >= 0 && written < static_cast<int>(LOG_BUFFER_SIZE)) {
vsnprintf(log_buffer + written, LOG_BUFFER_SIZE - written, format, args);
}
size_t len = strlen(log_buffer);
if (len == 0 || log_buffer[len - 1] != '\n') {
strncat(log_buffer, "\n", LOG_BUFFER_SIZE - len);
}
log_print(level, log_buffer, log_verbose, log_color);
va_end(args);
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef __EXAMPLE_LOG_H__
#define __EXAMPLE_LOG_H__
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif // _WIN32
#include "stable-diffusion.h"
extern bool log_verbose;
extern bool log_color;
std::string sd_basename(const std::string& path);
void print_utf8(FILE* stream, const char* utf8);
void log_print(sd_log_level_t level, const char* log, bool verbose, bool color);
void example_log_printf(sd_log_level_t level, const char* file, int line, const char* format, ...);
#define LOG_DEBUG(format, ...) example_log_printf(SD_LOG_DEBUG, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define LOG_INFO(format, ...) example_log_printf(SD_LOG_INFO, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define LOG_WARN(format, ...) example_log_printf(SD_LOG_WARN, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) example_log_printf(SD_LOG_ERROR, __FILE__, __LINE__, format, ##__VA_ARGS__)
#endif // __EXAMPLE_LOG_H__
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
#ifndef __MEDIA_IO_H__
#define __MEDIA_IO_H__
#include <cstdint>
#include <string>
#include <vector>
#include "stable-diffusion.h"
enum class EncodedImageFormat {
JPEG,
PNG,
WEBP,
UNKNOWN,
};
EncodedImageFormat encoded_image_format_from_path(const std::string& path);
std::vector<uint8_t> encode_image_to_vector(EncodedImageFormat format,
const uint8_t* image,
int width,
int height,
int channels,
const std::string& parameters = "",
int quality = 90);
bool write_image_to_file(const std::string& path,
const uint8_t* image,
int width,
int height,
int channels,
const std::string& parameters = "",
int quality = 90);
uint8_t* load_image_from_file(const char* image_path,
int& width,
int& height,
int expected_width = 0,
int expected_height = 0,
int expected_channel = 3);
bool load_sd_image_from_file(sd_image_t* image,
const char* image_path,
int expected_width = 0,
int expected_height = 0,
int expected_channel = 3);
uint8_t* load_image_from_memory(const char* image_bytes,
int len,
int& width,
int& height,
int expected_width = 0,
int expected_height = 0,
int expected_channel = 3);
int create_mjpg_avi_from_sd_images(const char* filename,
sd_image_t* images,
int num_images,
int fps,
int quality = 90);
#ifdef SD_USE_WEBP
int create_animated_webp_from_sd_images(const char* filename,
sd_image_t* images,
int num_images,
int fps,
int quality = 90);
#endif
#ifdef SD_USE_WEBM
int create_webm_from_sd_images(const char* filename,
sd_image_t* images,
int num_images,
int fps,
int quality = 90);
#endif
int create_video_from_sd_images(const char* filename,
sd_image_t* images,
int num_images,
int fps,
int quality = 90);
#endif // __MEDIA_IO_H__
+207
View File
@@ -0,0 +1,207 @@
#ifndef __EXAMPLE_RESOURCE_OWNERS_H__
#define __EXAMPLE_RESOURCE_OWNERS_H__
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <utility>
#include <vector>
#include "stable-diffusion.h"
struct FreeDeleter {
void operator()(void* ptr) const {
free(ptr);
}
};
struct FileCloser {
void operator()(FILE* file) const {
if (file != nullptr) {
fclose(file);
}
}
};
struct SDCtxDeleter {
void operator()(sd_ctx_t* ctx) const {
if (ctx != nullptr) {
free_sd_ctx(ctx);
}
}
};
struct UpscalerCtxDeleter {
void operator()(upscaler_ctx_t* ctx) const {
if (ctx != nullptr) {
free_upscaler_ctx(ctx);
}
}
};
template <typename T>
using FreeUniquePtr = std::unique_ptr<T, FreeDeleter>;
using FilePtr = std::unique_ptr<FILE, FileCloser>;
using SDCtxPtr = std::unique_ptr<sd_ctx_t, SDCtxDeleter>;
using UpscalerCtxPtr = std::unique_ptr<upscaler_ctx_t, UpscalerCtxDeleter>;
class SDImageOwner {
public:
SDImageOwner() = default;
explicit SDImageOwner(sd_image_t image)
: image_(image) {
}
SDImageOwner(const SDImageOwner&) = delete;
SDImageOwner& operator=(const SDImageOwner&) = delete;
SDImageOwner(SDImageOwner&& other) noexcept
: image_(other.release()) {
}
SDImageOwner& operator=(SDImageOwner&& other) noexcept {
if (this != &other) {
reset();
image_ = other.release();
}
return *this;
}
~SDImageOwner() {
reset();
}
sd_image_t* put() {
if (image_.data != nullptr) {
free(image_.data);
image_.data = nullptr;
}
image_.width = 0;
image_.height = 0;
return &image_;
}
sd_image_t& get() {
return image_;
}
const sd_image_t& get() const {
return image_;
}
sd_image_t release() {
sd_image_t image = image_;
image_ = {0, 0, 0, nullptr};
return image;
}
void reset(sd_image_t image = {0, 0, 0, nullptr}) {
if (image_.data != nullptr) {
free(image_.data);
}
image_ = image;
}
private:
sd_image_t image_ = {0, 0, 0, nullptr};
};
class SDImageVec {
public:
SDImageVec() = default;
SDImageVec(const SDImageVec&) = delete;
SDImageVec& operator=(const SDImageVec&) = delete;
SDImageVec(SDImageVec&& other) noexcept
: images_(std::move(other.images_)) {
}
SDImageVec& operator=(SDImageVec&& other) noexcept {
if (this != &other) {
clear();
images_ = std::move(other.images_);
}
return *this;
}
~SDImageVec() {
clear();
}
void push_back(sd_image_t image) {
images_.push_back(image);
}
void push_back(SDImageOwner&& image) {
images_.push_back(image.release());
}
void reserve(size_t count) {
images_.reserve(count);
}
void adopt(sd_image_t* images, int count) {
clear();
if (images == nullptr || count <= 0) {
free(images);
return;
}
images_.reserve(static_cast<size_t>(count));
for (int i = 0; i < count; ++i) {
images_.push_back(images[i]);
}
free(images);
}
size_t size() const {
return images_.size();
}
bool empty() const {
return images_.empty();
}
explicit operator bool() const {
return !images_.empty();
}
sd_image_t* data() {
return images_.data();
}
const sd_image_t* data() const {
return images_.data();
}
sd_image_t& operator[](size_t index) {
return images_[index];
}
const sd_image_t& operator[](size_t index) const {
return images_[index];
}
std::vector<sd_image_t>& raw() {
return images_;
}
const std::vector<sd_image_t>& raw() const {
return images_;
}
void clear() {
for (sd_image_t& image : images_) {
free(image.data);
image.data = nullptr;
}
images_.clear();
}
private:
std::vector<sd_image_t> images_;
};
#endif // __EXAMPLE_RESOURCE_OWNERS_H__
+15 -2
View File
@@ -56,7 +56,12 @@ else()
message(STATUS "Frontend disabled or directory not found: ${FRONTEND_DIR}")
endif()
add_executable(${TARGET} main.cpp)
add_executable(${TARGET}
../common/common.cpp
../common/log.cpp
../common/media_io.cpp
main.cpp
)
if(HAVE_FRONTEND_BUILD)
add_dependencies(${TARGET} ${TARGET}_frontend)
@@ -70,10 +75,18 @@ endif()
install(TARGETS ${TARGET} RUNTIME)
target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT})
if(SD_WEBP)
target_compile_definitions(${TARGET} PRIVATE SD_USE_WEBP)
target_link_libraries(${TARGET} PRIVATE webp libwebpmux)
endif()
if(SD_WEBM)
target_compile_definitions(${TARGET} PRIVATE SD_USE_WEBM)
target_link_libraries(${TARGET} PRIVATE webm)
endif()
# due to httplib; it contains a pragma for MSVC, but other things need explicit flags
if(WIN32 AND NOT MSVC)
target_link_libraries(${TARGET} PRIVATE ws2_32)
endif()
target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17)
target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17)
+2 -2
View File
@@ -189,7 +189,7 @@ Default Generation Options:
medium
--skip-layer-start <float> SLG enabling point (default: 0.01)
--skip-layer-end <float> SLG disabling point (default: 0.2)
--eta <float> eta in DDIM, only for DDIM and TCD (default: 0)
--eta <float> noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)
--flow-shift <float> shift value for Flow models like SD3.x or WAN (default: auto)
--high-noise-cfg-scale <float> (high noise) unconditional guidance scale: (default: 7.0)
--high-noise-img-cfg-scale <float> (high noise) image guidance scale for inpaint or instruct-pix2pix models (default: same as --cfg-scale)
@@ -197,7 +197,7 @@ Default Generation Options:
--high-noise-slg-scale <float> (high noise) skip layer guidance (SLG) scale, only for DiT models: (default: 0)
--high-noise-skip-layer-start <float> (high noise) SLG enabling point (default: 0.01)
--high-noise-skip-layer-end <float> (high noise) SLG disabling point (default: 0.2)
--high-noise-eta <float> (high noise) eta in DDIM, only for DDIM and TCD (default: 0)
--high-noise-eta <float> (high noise) noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)
--strength <float> strength for noising/unnoising (default: 0.75)
--pm-style-strength <float>
--control-strength <float> strength to apply Control Net (default: 0.9). 1.0 corresponds to full destruction of information in init image
+112 -179
View File
@@ -8,15 +8,19 @@
#include <sstream>
#include <vector>
#include <json.hpp>
#include "httplib.h"
#include "stable-diffusion.h"
#include "common/common.hpp"
#include "common/common.h"
#include "common/media_io.h"
#include "common/resource_owners.hpp"
#ifdef HAVE_INDEX_HTML
#include "frontend/dist/gen_index_html.h"
#endif
using json = nlohmann::json;
namespace fs = std::filesystem;
// ----------------------- helpers -----------------------
@@ -217,62 +221,6 @@ std::string extract_and_remove_sd_cpp_extra_args(std::string& text) {
return extracted;
}
enum class ImageFormat { JPEG,
PNG };
static int stbi_ext_write_png_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data, int stride_bytes, const char* parameters) {
int len;
unsigned char* png = stbi_write_png_to_mem((const unsigned char*)data, stride_bytes, x, y, comp, &len, parameters);
if (png == NULL)
return 0;
func(context, png, len);
STBIW_FREE(png);
return 1;
}
std::vector<uint8_t> write_image_to_vector(
ImageFormat format,
const uint8_t* image,
int width,
int height,
int channels,
std::string params = "",
int quality = 90) {
std::vector<uint8_t> buffer;
auto write_func = [&buffer](void* context, void* data, int size) {
uint8_t* src = reinterpret_cast<uint8_t*>(data);
buffer.insert(buffer.end(), src, src + size);
};
struct ContextWrapper {
decltype(write_func)& func;
} ctx{write_func};
auto c_func = [](void* context, void* data, int size) {
auto* wrapper = reinterpret_cast<ContextWrapper*>(context);
wrapper->func(context, data, size);
};
int result = 0;
switch (format) {
case ImageFormat::JPEG:
result = stbi_write_jpg_to_func(c_func, &ctx, width, height, channels, image, quality);
break;
case ImageFormat::PNG:
result = stbi_ext_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels, params.size() > 0 ? params.c_str() : nullptr);
break;
default:
throw std::runtime_error("invalid image format");
}
if (!result) {
throw std::runtime_error("write imgage to mem failed");
}
return buffer;
}
void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) {
SDSvrParams* svr_params = (SDSvrParams*)data;
log_print(level, log, svr_params->verbose, svr_params->color);
@@ -341,18 +289,6 @@ std::string get_lora_full_path(ServerRuntime& rt, const std::string& path) {
return (it != rt.lora_cache->end()) ? it->fullpath : "";
}
void free_results(sd_image_t* result_images, int num_results) {
if (result_images) {
for (int i = 0; i < num_results; ++i) {
if (result_images[i].data) {
stbi_image_free(result_images[i].data);
result_images[i].data = nullptr;
}
}
}
free(result_images);
}
void register_index_endpoints(httplib::Server& svr, const SDSvrParams& svr_params, const std::string& index_html) {
const std::string serve_html_path = svr_params.serve_html_path;
svr.Get("/", [serve_html_path, index_html](const httplib::Request&, httplib::Response& res) {
@@ -416,9 +352,9 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string sd_cpp_extra_args_str = extract_and_remove_sd_cpp_extra_args(prompt);
if (output_format != "png" && output_format != "jpeg") {
if (output_format != "png" && output_format != "jpeg" && output_format != "webp") {
res.status = 400;
res.set_content(R"({"error":"invalid output_format, must be one of [png, jpeg]"})", "application/json");
res.set_content(R"({"error":"invalid output_format, must be one of [png, jpeg, webp]"})", "application/json");
return;
}
if (n <= 0)
@@ -460,10 +396,10 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
LOG_DEBUG("%s\n", gen_params.to_string().c_str());
sd_image_t init_image = {(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr};
sd_image_t control_image = {(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr};
sd_image_t mask_image = {(uint32_t)gen_params.width, (uint32_t)gen_params.height, 1, nullptr};
std::vector<sd_image_t> pmid_images;
SDImageOwner init_image({(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr});
SDImageOwner control_image({(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr});
SDImageOwner mask_image({(uint32_t)gen_params.width, (uint32_t)gen_params.height, 1, nullptr});
SDImageVec pmid_images;
sd_img_gen_params_t img_gen_params = {
gen_params.lora_vec.data(),
@@ -471,19 +407,19 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.prompt.c_str(),
gen_params.negative_prompt.c_str(),
gen_params.clip_skip,
init_image,
init_image.get(),
nullptr,
0,
gen_params.auto_resize_ref_image,
gen_params.increase_ref_index,
mask_image,
mask_image.get(),
gen_params.width,
gen_params.height,
gen_params.sample_params,
gen_params.strength,
gen_params.seed,
gen_params.batch_count,
control_image,
control_image.get(),
gen_params.control_strength,
{
pmid_images.data(),
@@ -495,13 +431,19 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.cache_params,
};
sd_image_t* results = nullptr;
int num_results = 0;
SDImageVec results;
int num_results = 0;
{
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
results = generate_image(runtime->sd_ctx, &img_gen_params);
num_results = gen_params.batch_count;
results.adopt(generate_image(runtime->sd_ctx, &img_gen_params), num_results);
}
if (!results) {
res.status = 500;
res.set_content(R"({"error":"generate failed"})", "application/json");
return;
}
for (int i = 0; i < num_results; i++) {
@@ -511,13 +453,17 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string params = gen_params.embed_image_metadata
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
: "";
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params,
output_compression);
auto image_bytes = encode_image_to_vector(output_format == "jpeg"
? EncodedImageFormat::JPEG
: output_format == "webp"
? EncodedImageFormat::WEBP
: EncodedImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params,
output_compression);
if (image_bytes.empty()) {
LOG_ERROR("write image to mem failed");
continue;
@@ -528,8 +474,6 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
item["b64_json"] = b64;
out["data"].push_back(item);
}
free_results(results, num_results);
res.set_content(out.dump(), "application/json");
res.status = 200;
@@ -650,9 +594,9 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
LOG_DEBUG("%s\n", gen_params.to_string().c_str());
sd_image_t init_image = {0, 0, 3, nullptr};
sd_image_t control_image = {0, 0, 3, nullptr};
std::vector<sd_image_t> pmid_images;
SDImageOwner init_image({0, 0, 3, nullptr});
SDImageOwner control_image({0, 0, 3, nullptr});
SDImageVec pmid_images;
auto get_resolved_width = [&gen_params, runtime]() -> int {
if (gen_params.width > 0)
@@ -669,7 +613,7 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
return 512;
};
std::vector<sd_image_t> ref_images;
SDImageVec ref_images;
ref_images.reserve(images_bytes.size());
for (auto& bytes : images_bytes) {
int img_w;
@@ -685,12 +629,12 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
continue;
}
sd_image_t img{(uint32_t)img_w, (uint32_t)img_h, 3, raw_pixels};
gen_params.set_width_and_height_if_unset(img.width, img.height);
ref_images.push_back(img);
SDImageOwner img({(uint32_t)img_w, (uint32_t)img_h, 3, raw_pixels});
gen_params.set_width_and_height_if_unset(img.get().width, img.get().height);
ref_images.push_back(std::move(img));
}
sd_image_t mask_image = {0};
SDImageOwner mask_image({0, 0, 1, nullptr});
if (!mask_bytes.empty()) {
int expected_width = 0;
int expected_height = 0;
@@ -706,13 +650,10 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
static_cast<int>(mask_bytes.size()),
mask_w, mask_h,
expected_width, expected_height, 1);
mask_image = {(uint32_t)mask_w, (uint32_t)mask_h, 1, mask_raw};
gen_params.set_width_and_height_if_unset(mask_image.width, mask_image.height);
mask_image.reset({(uint32_t)mask_w, (uint32_t)mask_h, 1, mask_raw});
gen_params.set_width_and_height_if_unset(mask_image.get().width, mask_image.get().height);
} else {
mask_image.width = get_resolved_width();
mask_image.height = get_resolved_height();
mask_image.channel = 1;
mask_image.data = nullptr;
mask_image.reset({(uint32_t)get_resolved_width(), (uint32_t)get_resolved_height(), 1, nullptr});
}
sd_img_gen_params_t img_gen_params = {
@@ -721,19 +662,19 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.prompt.c_str(),
gen_params.negative_prompt.c_str(),
gen_params.clip_skip,
init_image,
init_image.get(),
ref_images.data(),
(int)ref_images.size(),
gen_params.auto_resize_ref_image,
gen_params.increase_ref_index,
mask_image,
mask_image.get(),
get_resolved_width(),
get_resolved_height(),
gen_params.sample_params,
gen_params.strength,
gen_params.seed,
gen_params.batch_count,
control_image,
control_image.get(),
gen_params.control_strength,
{
pmid_images.data(),
@@ -745,13 +686,19 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.cache_params,
};
sd_image_t* results = nullptr;
int num_results = 0;
SDImageVec results;
int num_results = 0;
{
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
results = generate_image(runtime->sd_ctx, &img_gen_params);
num_results = gen_params.batch_count;
results.adopt(generate_image(runtime->sd_ctx, &img_gen_params), num_results);
}
if (!results) {
res.status = 500;
res.set_content(R"({"error":"generate failed"})", "application/json");
return;
}
json out;
@@ -765,32 +712,24 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string params = gen_params.embed_image_metadata
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
: "";
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params,
output_compression);
auto image_bytes = encode_image_to_vector(output_format == "jpeg"
? EncodedImageFormat::JPEG
: output_format == "webp"
? EncodedImageFormat::WEBP
: EncodedImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params,
output_compression);
std::string b64 = base64_encode(image_bytes);
json item;
item["b64_json"] = b64;
out["data"].push_back(item);
}
free_results(results, num_results);
res.set_content(out.dump(), "application/json");
res.status = 200;
if (init_image.data) {
stbi_image_free(init_image.data);
}
if (mask_image.data) {
stbi_image_free(mask_image.data);
}
for (auto ref_image : ref_images) {
stbi_image_free(ref_image.data);
}
} catch (const std::exception& e) {
res.status = 500;
json err;
@@ -939,12 +878,11 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
LOG_DEBUG("%s\n", gen_params.to_string().c_str());
sd_image_t init_image = {0, 0, 3, nullptr};
sd_image_t control_image = {0, 0, 3, nullptr};
sd_image_t mask_image = {0, 0, 1, nullptr};
std::vector<uint8_t> mask_data;
std::vector<sd_image_t> pmid_images;
std::vector<sd_image_t> ref_images;
SDImageOwner init_image({0, 0, 3, nullptr});
SDImageOwner control_image({0, 0, 3, nullptr});
SDImageOwner mask_image({0, 0, 1, nullptr});
SDImageVec pmid_images;
SDImageVec ref_images;
auto get_resolved_width = [&gen_params, runtime]() -> int {
if (gen_params.width > 0)
@@ -961,7 +899,7 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
return 512;
};
auto decode_image = [&gen_params](sd_image_t& image, std::string encoded) -> bool {
auto decode_image = [&gen_params](SDImageOwner& image, std::string encoded) -> bool {
auto comma_pos = encoded.find(',');
if (comma_pos != std::string::npos) {
encoded = encoded.substr(comma_pos + 1);
@@ -980,10 +918,10 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
uint8_t* raw_data = load_image_from_memory(
(const char*)img_data.data(), (int)img_data.size(),
img_w, img_h,
expected_width, expected_height, image.channel);
expected_width, expected_height, image.get().channel);
if (raw_data) {
image = {(uint32_t)img_w, (uint32_t)img_h, image.channel, raw_data};
gen_params.set_width_and_height_if_unset(image.width, image.height);
image.reset({(uint32_t)img_w, (uint32_t)img_h, image.get().channel, raw_data});
gen_params.set_width_and_height_if_unset(image.get().width, image.get().height);
return true;
}
}
@@ -1000,19 +938,21 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string encoded = j["mask"].get<std::string>();
decode_image(mask_image, encoded);
bool inpainting_mask_invert = j.value("inpainting_mask_invert", 0) != 0;
if (inpainting_mask_invert && mask_image.data != nullptr) {
for (uint32_t i = 0; i < mask_image.width * mask_image.height; i++) {
mask_image.data[i] = 255 - mask_image.data[i];
if (inpainting_mask_invert && mask_image.get().data != nullptr) {
for (uint32_t i = 0; i < mask_image.get().width * mask_image.get().height; i++) {
mask_image.get().data[i] = 255 - mask_image.get().data[i];
}
}
} else {
int m_width = get_resolved_width();
int m_height = get_resolved_height();
mask_data = std::vector<uint8_t>(m_width * m_height, 255);
mask_image.width = m_width;
mask_image.height = m_height;
mask_image.channel = 1;
mask_image.data = mask_data.data();
int m_width = get_resolved_width();
int m_height = get_resolved_height();
sd_image_t generated_mask = {(uint32_t)m_width, (uint32_t)m_height, 1, nullptr};
generated_mask.data = (uint8_t*)malloc(static_cast<size_t>(m_width) * static_cast<size_t>(m_height));
if (generated_mask.data == nullptr) {
return bad("failed to allocate default mask");
}
memset(generated_mask.data, 255, static_cast<size_t>(m_width) * static_cast<size_t>(m_height));
mask_image.reset(generated_mask);
}
float denoising_strength = j.value("denoising_strength", -1.f);
@@ -1024,10 +964,10 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
if (j.contains("extra_images") && j["extra_images"].is_array()) {
for (auto extra_image : j["extra_images"]) {
std::string encoded = extra_image.get<std::string>();
sd_image_t tmp_image = {(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr};
std::string encoded = extra_image.get<std::string>();
SDImageOwner tmp_image({(uint32_t)gen_params.width, (uint32_t)gen_params.height, 3, nullptr});
if (decode_image(tmp_image, encoded)) {
ref_images.push_back(tmp_image);
ref_images.push_back(std::move(tmp_image));
}
}
}
@@ -1038,19 +978,19 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.prompt.c_str(),
gen_params.negative_prompt.c_str(),
gen_params.clip_skip,
init_image,
init_image.get(),
ref_images.data(),
(int)ref_images.size(),
gen_params.auto_resize_ref_image,
gen_params.increase_ref_index,
mask_image,
mask_image.get(),
get_resolved_width(),
get_resolved_height(),
gen_params.sample_params,
gen_params.strength,
gen_params.seed,
gen_params.batch_count,
control_image,
control_image.get(),
gen_params.control_strength,
{
pmid_images.data(),
@@ -1062,13 +1002,19 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
gen_params.cache_params,
};
sd_image_t* results = nullptr;
int num_results = 0;
SDImageVec results;
int num_results = 0;
{
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
results = generate_image(runtime->sd_ctx, &img_gen_params);
num_results = gen_params.batch_count;
results.adopt(generate_image(runtime->sd_ctx, &img_gen_params), num_results);
}
if (!results) {
res.status = 500;
res.set_content(R"({"error":"generate failed"})", "application/json");
return;
}
json out;
@@ -1084,12 +1030,12 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string params = gen_params.embed_image_metadata
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
: "";
auto image_bytes = write_image_to_vector(ImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params);
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
results[i].data,
results[i].width,
results[i].height,
results[i].channel,
params);
if (image_bytes.empty()) {
LOG_ERROR("write image to mem failed");
@@ -1099,21 +1045,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
std::string b64 = base64_encode(image_bytes);
out["images"].push_back(b64);
}
free_results(results, num_results);
res.set_content(out.dump(), "application/json");
res.status = 200;
if (init_image.data) {
stbi_image_free(init_image.data);
}
if (mask_image.data && mask_data.empty()) {
stbi_image_free(mask_image.data);
}
for (auto ref_image : ref_images) {
stbi_image_free(ref_image.data);
}
} catch (const std::exception& e) {
res.status = 500;
json err;
@@ -1225,7 +1159,7 @@ int main(int argc, const char** argv) {
LOG_DEBUG("%s", default_gen_params.to_string().c_str());
sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false, false, false);
sd_ctx_t* sd_ctx = new_sd_ctx(&sd_ctx_params);
SDCtxPtr sd_ctx(new_sd_ctx(&sd_ctx_params));
if (sd_ctx == nullptr) {
LOG_ERROR("new_sd_ctx_t failed");
@@ -1237,7 +1171,7 @@ int main(int argc, const char** argv) {
std::vector<LoraEntry> lora_cache;
std::mutex lora_mutex;
ServerRuntime runtime = {
sd_ctx,
sd_ctx.get(),
&sd_ctx_mutex,
&svr_params,
&ctx_params,
@@ -1278,6 +1212,5 @@ int main(int argc, const char** argv) {
LOG_INFO("listening on: %s:%d\n", svr_params.listen_ip.c_str(), svr_params.listen_port);
svr.listen(svr_params.listen_ip, svr_params.listen_port);
free_sd_ctx(sd_ctx);
return 0;
}
+3 -1
View File
@@ -1,4 +1,6 @@
for f in src/*.cpp src/*.h src/*.hpp src/vocab/*.h src/vocab/*.cpp examples/cli/*.cpp examples/common/*.hpp examples/cli/*.h examples/server/*.cpp; do
for f in src/*.cpp src/*.h src/*.hpp src/vocab/*.h src/vocab/*.cpp \
examples/cli/*.cpp examples/cli/*.h examples/server/*.cpp \
examples/common/*.hpp examples/common/*.h examples/common/*.cpp; do
[[ "$f" == vocab* ]] && continue
echo "formatting '$f'"
# if [ "$f" != "stable-diffusion.h" ]; then
+71 -9
View File
@@ -668,8 +668,8 @@ struct DiscreteFlowDenoiser : public Denoiser {
}
void set_parameters() {
for (int i = 1; i < TIMESTEPS + 1; i++) {
sigmas[i - 1] = t_to_sigma(static_cast<float>(i));
for (int i = 0; i < TIMESTEPS; i++) {
sigmas[i] = t_to_sigma(static_cast<float>(i));
}
}
@@ -786,10 +786,43 @@ static std::pair<float, float> get_ancestral_step(float sigma_from,
return {sigma_down, sigma_up};
}
static std::tuple<float, float, float> get_ancestral_step_flow(float sigma_from,
float sigma_to,
float eta = 1.0f) {
float sigma_down = sigma_to;
float sigma_up = 0.0f;
float alpha_scale = 1.0f;
if (eta <= 0.0f || sigma_from <= 0.0f || sigma_to <= 0.0f) {
return {sigma_down, sigma_up, alpha_scale};
}
// Flow Euler ancestral sampling becomes numerically unstable for eta > 1, so
// clamp to the valid maximum-noise regime instead of letting NaNs propagate.
eta = std::min(eta, 1.0f);
float sigma_ratio = sigma_to / sigma_from;
sigma_down = sigma_to * (1.0f + (sigma_ratio - 1.0f) * eta);
sigma_down = std::max(0.0f, std::min(sigma_to, sigma_down));
float denom = 1.0f - sigma_down;
if (denom <= 0.0f) {
return {sigma_to, sigma_up, alpha_scale};
}
alpha_scale = (1.0f - sigma_to) / denom;
float term = (sigma_down / sigma_to) * alpha_scale;
term = std::max(-1.0f, std::min(1.0f, term));
sigma_up = sigma_to * std::sqrt(std::max(1.0f - term * term, 0.0f));
return {sigma_down, sigma_up, alpha_scale};
}
static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
sd::Tensor<float> x,
const std::vector<float>& sigmas,
std::shared_ptr<RNG> rng) {
std::shared_ptr<RNG> rng,
float eta) {
int steps = static_cast<int>(sigmas.size()) - 1;
for (int i = 0; i < steps; i++) {
float sigma = sigmas[i];
@@ -799,7 +832,7 @@ static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
}
sd::Tensor<float> denoised = std::move(denoised_opt);
sd::Tensor<float> d = (x - denoised) / sigma;
auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1]);
auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1], eta);
x += d * (sigma_down - sigmas[i]);
if (sigmas[i + 1] > 0) {
x += sd::Tensor<float>::randn_like(x, rng) * sigma_up;
@@ -808,6 +841,30 @@ static sd::Tensor<float> sample_euler_ancestral(denoise_cb_t model,
return x;
}
static sd::Tensor<float> sample_euler_flow(denoise_cb_t model,
sd::Tensor<float> x,
const std::vector<float>& sigmas,
std::shared_ptr<RNG> rng,
float eta) {
int steps = static_cast<int>(sigmas.size()) - 1;
for (int i = 0; i < steps; i++) {
float sigma = sigmas[i];
auto denoised_opt = model(x, sigma, i + 1);
if (denoised_opt.empty()) {
return {};
}
sd::Tensor<float> denoised = std::move(denoised_opt);
auto [sigma_down, sigma_up, alpha_scale] = get_ancestral_step_flow(sigma, sigmas[i + 1], eta);
float sigma_ratio = sigma_down / sigma;
x = sigma_ratio * x + (1.0f - sigma_ratio) * denoised;
if (sigma_up > 0.0f) {
x = alpha_scale * x + sd::Tensor<float>::randn_like(x, rng) * sigma_up;
}
}
return x;
}
static sd::Tensor<float> sample_euler(denoise_cb_t model,
sd::Tensor<float> x,
const std::vector<float>& sigmas) {
@@ -885,7 +942,8 @@ static sd::Tensor<float> sample_dpm2(denoise_cb_t model,
static sd::Tensor<float> sample_dpmpp_2s_ancestral(denoise_cb_t model,
sd::Tensor<float> x,
const std::vector<float>& sigmas,
std::shared_ptr<RNG> rng) {
std::shared_ptr<RNG> rng,
float eta) {
auto t_fn = [](float sigma) -> float { return -log(sigma); };
auto sigma_fn = [](float t) -> float { return exp(-t); };
@@ -896,7 +954,7 @@ static sd::Tensor<float> sample_dpmpp_2s_ancestral(denoise_cb_t model,
return {};
}
sd::Tensor<float> denoised = std::move(denoised_opt);
auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1]);
auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1], eta);
if (sigma_down == 0) {
x = denoised;
@@ -1368,10 +1426,14 @@ static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
sd::Tensor<float> x,
std::vector<float> sigmas,
std::shared_ptr<RNG> rng,
float eta) {
float eta,
bool is_flow_denoiser) {
switch (method) {
case EULER_A_SAMPLE_METHOD:
return sample_euler_ancestral(model, std::move(x), sigmas, rng);
if (is_flow_denoiser)
return sample_euler_flow(model, std::move(x), sigmas, rng, eta);
else
return sample_euler_ancestral(model, std::move(x), sigmas, rng, eta);
case EULER_SAMPLE_METHOD:
return sample_euler(model, std::move(x), sigmas);
case HEUN_SAMPLE_METHOD:
@@ -1379,7 +1441,7 @@ static sd::Tensor<float> sample_k_diffusion(sample_method_t method,
case DPM2_SAMPLE_METHOD:
return sample_dpm2(model, std::move(x), sigmas);
case DPMPP2S_A_SAMPLE_METHOD:
return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, rng);
return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, rng, eta);
case DPMPP2M_SAMPLE_METHOD:
return sample_dpmpp_2m(model, std::move(x), sigmas);
case DPMPP2Mv2_SAMPLE_METHOD:
+49 -6
View File
@@ -1593,6 +1593,7 @@ public:
float eta,
int shifted_timestep,
sample_method_t method,
bool is_flow_denoiser,
const std::vector<float>& sigmas,
int start_merge_step,
const std::vector<sd::Tensor<float>>& ref_latents,
@@ -1791,7 +1792,7 @@ public:
return denoised;
};
auto x0_opt = sample_k_diffusion(method, denoise, x_t, sigmas, sampler_rng, eta);
auto x0_opt = sample_k_diffusion(method, denoise, x_t, sigmas, sampler_rng, eta, is_flow_denoiser);
if (x0_opt.empty()) {
LOG_ERROR("Diffusion model sampling failed");
if (control_net) {
@@ -1909,6 +1910,11 @@ public:
flow_denoiser->set_shift(flow_shift);
}
}
bool is_flow_denoiser() {
auto flow_denoiser = std::dynamic_pointer_cast<DiscreteFlowDenoiser>(denoiser);
return !!flow_denoiser;
}
};
/*================================================= SD API ==================================================*/
@@ -2225,6 +2231,7 @@ void sd_sample_params_init(sd_sample_params_t* sample_params) {
sample_params->scheduler = SCHEDULER_COUNT;
sample_params->sample_method = SAMPLE_METHOD_COUNT;
sample_params->sample_steps = 20;
sample_params->eta = INFINITY;
sample_params->custom_sigmas = nullptr;
sample_params->custom_sigmas_count = 0;
sample_params->flow_shift = INFINITY;
@@ -2438,6 +2445,26 @@ static scheduler_t resolve_scheduler(sd_ctx_t* sd_ctx,
return scheduler;
}
static float resolve_eta(sd_ctx_t* sd_ctx,
float eta,
enum sample_method_t sample_method) {
if (eta == INFINITY) {
switch (sample_method) {
case DDIM_TRAILING_SAMPLE_METHOD:
case TCD_SAMPLE_METHOD:
case RES_MULTISTEP_SAMPLE_METHOD:
case RES_2S_SAMPLE_METHOD:
return 0.0f;
case EULER_A_SAMPLE_METHOD:
case DPMPP2S_A_SAMPLE_METHOD:
return 1.0f;
default:;
}
return 0.0f;
}
return eta;
}
struct GenerationRequest {
std::string prompt;
std::string negative_prompt;
@@ -2586,6 +2613,8 @@ struct GenerationRequest {
struct SamplePlan {
enum sample_method_t sample_method = SAMPLE_METHOD_COUNT;
enum sample_method_t high_noise_sample_method = SAMPLE_METHOD_COUNT;
float eta = 0.f;
float high_noise_eta = 0.f;
int sample_steps = 0;
int high_noise_sample_steps = 0;
int total_steps = 0;
@@ -2597,6 +2626,7 @@ struct SamplePlan {
const sd_img_gen_params_t* sd_img_gen_params,
const GenerationRequest& request) {
sample_method = sd_img_gen_params->sample_params.sample_method;
eta = sd_img_gen_params->sample_params.eta;
sample_steps = sd_img_gen_params->sample_params.sample_steps;
resolve(sd_ctx, &request, &sd_img_gen_params->sample_params);
}
@@ -2605,10 +2635,12 @@ struct SamplePlan {
const sd_vid_gen_params_t* sd_vid_gen_params,
const GenerationRequest& request) {
sample_method = sd_vid_gen_params->sample_params.sample_method;
eta = sd_vid_gen_params->sample_params.eta;
sample_steps = sd_vid_gen_params->sample_params.sample_steps;
if (sd_ctx->sd->high_noise_diffusion_model) {
high_noise_sample_steps = sd_vid_gen_params->high_noise_sample_params.sample_steps;
high_noise_sample_method = sd_vid_gen_params->high_noise_sample_params.sample_method;
high_noise_eta = sd_vid_gen_params->high_noise_sample_params.eta;
}
moe_boundary = sd_vid_gen_params->moe_boundary;
resolve(sd_ctx, &request, &sd_vid_gen_params->sample_params);
@@ -2644,6 +2676,8 @@ struct SamplePlan {
sd_ctx->sd->version);
}
eta = resolve_eta(sd_ctx, eta, sample_method);
if (high_noise_sample_steps < 0) {
for (size_t i = 0; i < sigmas.size(); ++i) {
if (sigmas[i] < moe_boundary) {
@@ -2658,6 +2692,7 @@ struct SamplePlan {
if (high_noise_sample_steps > 0) {
high_noise_sample_method = resolve_sample_method(sd_ctx,
high_noise_sample_method);
high_noise_eta = resolve_eta(sd_ctx, high_noise_eta, high_noise_sample_method);
LOG_INFO("sampling(high noise) using %s method", sampling_methods_str[high_noise_sample_method]);
}
@@ -2811,7 +2846,8 @@ static std::optional<ImageGenerationLatents> prepare_image_generation_latents(sd
{request->width / request->vae_scale_factor,
request->height / request->vae_scale_factor,
1,
1});
1},
sd::ops::InterpolateMode::NearestMax);
sd::Tensor<float> init_latent;
sd::Tensor<float> control_latent;
@@ -2956,8 +2992,12 @@ static std::optional<ImageGenerationLatents> prepare_image_generation_latents(sd
latents.ref_latents = std::move(ref_latents);
if (sd_version_is_inpaint(sd_ctx->sd->version)) {
latents.denoise_mask = std::move(latent_mask);
latent_mask = sd::ops::max_pool_2d(latent_mask,
{3, 3},
{1, 1},
{1, 1});
}
latents.denoise_mask = std::move(latent_mask);
return latents;
}
@@ -3123,9 +3163,10 @@ SD_API sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* s
latents.control_image,
request.control_strength,
request.guidance,
request.eta,
plan.eta,
request.shifted_timestep,
plan.sample_method,
sd_ctx->sd->is_flow_denoiser(),
plan.sigmas,
plan.start_merge_step,
latents.ref_latents,
@@ -3482,9 +3523,10 @@ SD_API sd_image_t* generate_video(sd_ctx_t* sd_ctx, const sd_vid_gen_params_t* s
sd::Tensor<float>(),
0.f,
request.high_noise_guidance,
sd_vid_gen_params->high_noise_sample_params.eta,
plan.high_noise_eta,
request.shifted_timestep,
plan.high_noise_sample_method,
sd_ctx->sd->is_flow_denoiser(),
high_noise_sigmas,
-1,
std::vector<sd::Tensor<float>>{},
@@ -3523,9 +3565,10 @@ SD_API sd_image_t* generate_video(sd_ctx_t* sd_ctx, const sd_vid_gen_params_t* s
sd::Tensor<float>(),
0.f,
sd_vid_gen_params->sample_params.guidance,
sd_vid_gen_params->sample_params.eta,
plan.eta,
sd_vid_gen_params->sample_params.shifted_timestep,
plan.sample_method,
sd_ctx->sd->is_flow_denoiser(),
plan.sigmas,
-1,
std::vector<sd::Tensor<float>>{},
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef __T5_HPP__
#ifndef __T5_HPP__
#define __T5_HPP__
#include <cfloat>
+186 -13
View File
@@ -815,6 +815,9 @@ namespace sd {
namespace ops {
enum class InterpolateMode {
Nearest,
NearestMax,
NearestMin,
NearestAvg,
};
inline int64_t normalize_slice_bound(int64_t index, int64_t dim_size) {
@@ -1012,12 +1015,16 @@ namespace sd {
std::vector<int64_t> output_shape,
InterpolateMode mode = InterpolateMode::Nearest,
bool align_corners = false) {
if (mode != InterpolateMode::Nearest) {
tensor_throw_invalid_argument("Only nearest interpolate mode is implemented, got mode=" +
const bool is_nearest_like_mode = (mode == InterpolateMode::Nearest ||
mode == InterpolateMode::NearestMax ||
mode == InterpolateMode::NearestMin ||
mode == InterpolateMode::NearestAvg);
if (!is_nearest_like_mode) {
tensor_throw_invalid_argument("Only nearest-like interpolate modes are implemented, got mode=" +
std::to_string(static_cast<int>(mode)));
}
if (align_corners) {
tensor_throw_invalid_argument("align_corners is not supported for nearest interpolate: input_shape=" +
tensor_throw_invalid_argument("align_corners is not supported for nearest-like interpolate: input_shape=" +
tensor_shape_to_string(input.shape()) + ", output_shape=" +
tensor_shape_to_string(output_shape));
}
@@ -1044,14 +1051,102 @@ namespace sd {
}
}
Tensor<T> output(std::move(output_shape));
for (int64_t flat = 0; flat < output.numel(); ++flat) {
std::vector<int64_t> output_coord = tensor_unravel_index(flat, output.shape());
std::vector<int64_t> input_coord(static_cast<size_t>(input.dim()), 0);
for (size_t i = 0; i < static_cast<size_t>(input.dim()); ++i) {
input_coord[i] = output_coord[i] * input.shape()[i] / output.shape()[i];
bool has_downsampling = false;
for (int64_t i = 0; i < input.dim(); ++i) {
if (input.shape()[i] > output_shape[i]) {
has_downsampling = true;
break;
}
output[flat] = input.index(input_coord);
}
Tensor<T> output(std::move(output_shape));
if (mode == InterpolateMode::Nearest || !has_downsampling) {
for (int64_t flat = 0; flat < output.numel(); ++flat) {
std::vector<int64_t> output_coord = tensor_unravel_index(flat, output.shape());
std::vector<int64_t> input_coord(static_cast<size_t>(input.dim()), 0);
for (size_t i = 0; i < static_cast<size_t>(input.dim()); ++i) {
input_coord[i] = output_coord[i] * input.shape()[i] / output.shape()[i];
}
output[flat] = input.index(input_coord);
}
return output;
}
auto init_reduction = [&]() -> T {
switch (mode) {
case InterpolateMode::NearestMax:
return std::numeric_limits<T>::lowest();
case InterpolateMode::NearestMin:
return std::numeric_limits<T>::max();
case InterpolateMode::NearestAvg:
return T(0);
case InterpolateMode::Nearest:
return T(0);
}
tensor_throw_invalid_argument("Unsupported interpolate mode: mode=" +
std::to_string(static_cast<int>(mode)));
};
auto reduce_value = [&](T& acc, const T& sample) {
switch (mode) {
case InterpolateMode::NearestMax:
acc = std::max(acc, sample);
break;
case InterpolateMode::NearestMin:
acc = std::min(acc, sample);
break;
case InterpolateMode::NearestAvg:
acc += sample;
break;
case InterpolateMode::Nearest:
break;
}
};
// Reduction modes only differ from nearest mode when downsampling.
for (int64_t flat_out = 0; flat_out < output.numel(); ++flat_out) {
std::vector<int64_t> output_coord = tensor_unravel_index(flat_out, output.shape());
std::vector<int64_t> input_start(output.dim(), 0);
std::vector<int64_t> input_end(output.dim(), 0);
for (size_t i = 0; i < static_cast<size_t>(output.dim()); ++i) {
const int64_t input_dim = input.shape()[i];
const int64_t output_dim = output.shape()[i];
input_start[i] = std::max(int64_t(0), static_cast<int64_t>(output_coord[i] * input_dim / output_dim));
input_end[i] = std::min(input_dim, ((output_coord[i] + 1) * input_dim + output_dim - 1) / output_dim);
}
T value = init_reduction();
bool done_window = false;
std::vector<int64_t> current_in_coord = input_start;
while (!done_window) {
reduce_value(value, input.index(current_in_coord));
for (int d = static_cast<int>(output.dim()) - 1; d >= 0; --d) {
if (++current_in_coord[d] < input_end[d]) {
break;
}
current_in_coord[d] = input_start[d];
if (d == 0) {
done_window = true;
}
}
}
if (mode == InterpolateMode::NearestAvg) {
int64_t window_size = 1;
for (size_t i = 0; i < static_cast<size_t>(output.dim()); ++i) {
window_size *= (input_end[i] - input_start[i]);
}
value /= static_cast<T>(window_size);
}
output[flat_out] = value;
}
return output;
@@ -1063,12 +1158,16 @@ namespace sd {
const std::optional<std::vector<double>>& scale_factor,
InterpolateMode mode = InterpolateMode::Nearest,
bool align_corners = false) {
if (mode != InterpolateMode::Nearest) {
tensor_throw_invalid_argument("Only nearest interpolate mode is implemented, got mode=" +
const bool is_nearest_like_mode = (mode == InterpolateMode::Nearest ||
mode == InterpolateMode::NearestMax ||
mode == InterpolateMode::NearestMin ||
mode == InterpolateMode::NearestAvg);
if (!is_nearest_like_mode) {
tensor_throw_invalid_argument("Only nearest-like interpolate modes are implemented, got mode=" +
std::to_string(static_cast<int>(mode)));
}
if (align_corners) {
tensor_throw_invalid_argument("align_corners is not supported for nearest interpolate: input_shape=" +
tensor_throw_invalid_argument("align_corners is not supported for nearest-like interpolate: input_shape=" +
tensor_shape_to_string(input.shape()));
}
if (size.has_value() == scale_factor.has_value()) {
@@ -1128,6 +1227,80 @@ namespace sd {
align_corners);
}
template <typename T>
inline Tensor<T> max_pool_2d(const Tensor<T>& input,
std::vector<int64_t> kernel_size,
std::vector<int64_t> stride,
std::vector<int64_t> padding) {
if (input.dim() < 2) {
tensor_throw_invalid_argument("Tensor max_pool_2d requires input_dim >= 2: input_dim=" +
std::to_string(input.dim()) + ", input_shape=" +
tensor_shape_to_string(input.shape()));
}
if (kernel_size.size() != 2 || stride.size() != 2 || padding.size() != 2) {
tensor_throw_invalid_argument("Tensor max_pool_2d requires kernel_size, stride, and padding to have length 2");
}
for (size_t i = 0; i < 2; ++i) {
if (kernel_size[i] <= 0) {
tensor_throw_invalid_argument("Tensor max_pool_2d kernel_size must be positive: kernel_size=" +
tensor_shape_to_string(kernel_size));
}
if (stride[i] <= 0) {
tensor_throw_invalid_argument("Tensor max_pool_2d stride must be positive: stride=" +
tensor_shape_to_string(stride));
}
if (padding[i] < 0) {
tensor_throw_invalid_argument("Tensor max_pool_2d padding must be non-negative: padding=" +
tensor_shape_to_string(padding));
}
}
const int64_t in_height = input.shape()[0];
const int64_t in_width = input.shape()[1];
const int64_t out_height = (in_height + 2 * padding[0] - kernel_size[0]) / stride[0] + 1;
const int64_t out_width = (in_width + 2 * padding[1] - kernel_size[1]) / stride[1] + 1;
if (out_height <= 0 || out_width <= 0) {
tensor_throw_invalid_argument("max_pool_2d results in invalid output dimensions: " +
std::to_string(out_height) + "x" + std::to_string(out_width));
}
std::vector<int64_t> output_shape = input.shape();
output_shape[0] = out_height;
output_shape[1] = out_width;
Tensor<T> output(std::move(output_shape));
for (int64_t flat_out = 0; flat_out < output.numel(); ++flat_out) {
std::vector<int64_t> output_coord = tensor_unravel_index(flat_out, output.shape());
std::vector<int64_t> input_coord = output_coord;
const int64_t oh = output_coord[0];
const int64_t ow = output_coord[1];
T max_val = std::numeric_limits<T>::lowest();
bool has_valid_input = false;
for (int64_t kh = 0; kh < kernel_size[0]; ++kh) {
for (int64_t kw = 0; kw < kernel_size[1]; ++kw) {
const int64_t ih = oh * stride[0] + kh - padding[0];
const int64_t iw = ow * stride[1] + kw - padding[1];
if (ih >= 0 && ih < in_height && iw >= 0 && iw < in_width) {
input_coord[0] = ih;
input_coord[1] = iw;
max_val = std::max(max_val, input.index(input_coord));
has_valid_input = true;
}
}
}
output[flat_out] = has_valid_input ? max_val : T(0);
}
return output;
}
template <typename T>
inline Tensor<T> concat(const Tensor<T>& lhs, const Tensor<T>& rhs, size_t dim) {
if (lhs.dim() != rhs.dim()) {
+1 -1
View File
@@ -1,4 +1,4 @@
#include <algorithm>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
+43 -1
View File
@@ -1,3 +1,45 @@
set(Z_TARGET zip)
add_library(${Z_TARGET} OBJECT zip.c zip.h miniz.h)
target_include_directories(${Z_TARGET} PUBLIC .)
target_include_directories(${Z_TARGET} PUBLIC .)
if(SD_WEBP AND NOT SD_USE_SYSTEM_WEBP)
set(WEBP_BUILD_ANIM_UTILS OFF)
set(WEBP_BUILD_CWEBP OFF)
set(WEBP_BUILD_DWEBP OFF)
set(WEBP_BUILD_GIF2WEBP OFF)
set(WEBP_BUILD_IMG2WEBP OFF)
set(WEBP_BUILD_VWEBP OFF)
set(WEBP_BUILD_WEBPINFO OFF)
set(WEBP_BUILD_WEBPMUX OFF)
set(WEBP_BUILD_EXTRAS OFF)
set(WEBP_BUILD_WEBP_JS OFF)
set(WEBP_BUILD_FUZZTEST OFF)
set(WEBP_BUILD_LIBWEBPMUX ON)
add_subdirectory(libwebp EXCLUDE_FROM_ALL)
endif()
if(SD_WEBM AND NOT SD_USE_SYSTEM_WEBM)
if(MSVC)
set(MSVC_RUNTIME dll)
endif()
set(ENABLE_WEBMTS OFF)
set(ENABLE_WEBMINFO OFF)
set(ENABLE_TESTS OFF)
set(ENABLE_WEBM_PARSER OFF)
set(ENABLE_SAMPLE_PROGRAMS OFF)
set(SD_LIBWEBM_PARENT_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
add_subdirectory(libwebm EXCLUDE_FROM_ALL)
# libwebm mutates the global CMAKE_CXX_FLAGS for non-MSVC compilers to force
# C++11. Restore the parent flags so the main project keeps its own C++17
# requirements, then pin the libwebm targets to C++17 explicitly.
set(CMAKE_CXX_FLAGS "${SD_LIBWEBM_PARENT_CXX_FLAGS}" CACHE STRING "" FORCE)
target_compile_features(mkvmuxer PRIVATE cxx_std_17)
target_compile_features(mkvparser PRIVATE cxx_std_17)
target_compile_features(webm PRIVATE cxx_std_17)
target_include_directories(webm INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/libwebm)
endif()
Vendored Submodule
+1
Submodule thirdparty/libwebm added at 5bf12267ee
Vendored Submodule
+1
Submodule thirdparty/libwebp added at 0c9546f7ef