Compare commits

..
34 changed files with 548 additions and 113 deletions
+2
View File
@@ -15,6 +15,7 @@ API and command-line option may change frequently.***
## 🔥Important News
* **2026/10/16** 🚀 stable-diffusion.cpp adds **Day-0 support for FLUX.3 Image Klein / Klein Base**
* **2026/09/20** 🚀 stable-diffusion.cpp adds **Day-0 support for Qwen-Image-2.1**
* **2026/08/20** 🚀 stable-diffusion.cpp now supports **LTX-2.5**
* **2026/08/04** 🚀 stable-diffusion.cpp adds **Day-1 support for MiniMax-H3**
@@ -44,6 +45,7 @@ API and command-line option may change frequently.***
- [SD3/SD3.5](./docs/sd3.md)
- [FLUX.1-dev/FLUX.1-schnell](./docs/flux.md)
- [FLUX.2-dev/FLUX.2-klein](./docs/flux2.md)
- [FLUX.3 Image Klein / Klein Base](./docs/flux3.md)
- [Lens](./docs/lens.md)
- [Chroma](./docs/chroma.md)
- [Chroma1-Radiance](./docs/chroma_radiance.md)
Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

+1
View File
@@ -14,6 +14,7 @@ Depending on the architecture, different models handle reference images differen
| [**Qwen Image Edit**](./qwen_image_edit.md) | `qwen` |
| **Qwen Image LAYERED** | `qwen_layered` |
| [**Flux.2 [Dev] / Flux.2 [Klein]**](./flux2.md) | `flux2` |
| [**FLUX.3 Image Klein / Klein Base**](./flux3.md) | `flux2` |
| [**Boogu Image Edit**](./boogu_image.md) | `z_image_omni` |
| **Krea2 (Community Edit LoRAs)** | `krea2_ostris_edit` |
| [**Mage-Flow-Edit**](./mage_flow.md#image-editing) | `mage_flow` |
+48
View File
@@ -0,0 +1,48 @@
# How to Use
FLUX.3 Image Klein uses a FLUX.3 diffusion transformer, the FLUX.2 VAE, and Qwen3-VL 4B as the LLM text encoder.
## Download weights
- Download FLUX.3 Image Klein
- safetensors: https://huggingface.co/black-forest-labs/flux-3-image-klein/tree/main
- Download FLUX.3 Image Klein Base
- safetensors: https://huggingface.co/black-forest-labs/flux-3-image-klein-base/tree/main
- Download vae
- safetensors: https://huggingface.co/black-forest-labs/FLUX.2-dev/tree/main
- The `ae.safetensors` included with FLUX.3 can also be used.
- Download Qwen3-VL 4B
- safetensors: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders
- gguf: https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/tree/main
Use the root `model.safetensors` for the diffusion model.
## Examples
### FLUX.3 Image Klein
The distilled model uses 16 steps and CFG 1.0.
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p "a lovely cat holding a sign says 'flux3.cpp'" --cfg-scale 1.0 --steps 16 --sampling-method euler -v --offload-to-cpu
```
<img src="../assets/flux3/flux3-image-klein.png" />
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein.safetensors --vae ..\models\vae\flux2_ae.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'flux3-image-klein.cpp'" --cfg-scale 1.0 --sampling-method euler -v --offload-to-cpu --steps 16
```
<img src="../assets/flux3/flux3-image-klein-edit.png" />
Repeat `-r` to use multiple reference images, up to 10. See [Image Editing](./edit.md) for reference-image preprocessing options.
### FLUX.3 Image Klein Base
```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\flux-3-image-klein-base.safetensors --llm ..\models\text_encoders\Qwen3-VL-4B-Instruct-Q4_K_M.gguf --vae ..\models\vae\flux2_ae.safetensors -p "a lovely cat holding a sign says 'flux3.cpp'" --cfg-scale 3.5 --steps 20 --sampling-method euler -v --offload-to-cpu
```
<img src="../assets/flux3/flux3-image-klein-base.png" />
The default discrete scheduler uses the upstream time shift of 6.93 for both models. Use `--flow-shift` to override it.
+2 -1
View File
@@ -4,6 +4,7 @@
#include <cctype>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
@@ -276,7 +277,7 @@ namespace {
}
bool read_file(const std::string& path, std::vector<uint8_t>& data, std::string& error) {
std::ifstream fin(path, std::ios::binary);
std::ifstream fin(std::filesystem::u8path(path), std::ios::binary);
if (!fin) {
error = "failed to open file: " + path;
return false;
+31 -30
View File
@@ -188,7 +188,7 @@ struct SDCliParams {
if (++index >= argc) {
return -1;
}
imatrix_in.push_back(argv[index]);
imatrix_in.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -323,13 +323,14 @@ 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<SDImageOwner>& images,
int max_image_num = 0) {
if (!fs::exists(dir) || !fs::is_directory(dir)) {
const fs::path dir_path = fs::u8path(dir);
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
LOG_ERROR("'%s' is not a valid directory\n", dir.c_str());
return false;
}
std::vector<fs::directory_entry> entries;
for (const auto& entry : fs::directory_iterator(dir)) {
for (const auto& entry : fs::directory_iterator(dir_path)) {
if (entry.is_regular_file()) {
entries.push_back(entry);
}
@@ -337,12 +338,12 @@ bool load_images_from_dir(const std::string dir,
std::sort(entries.begin(), entries.end(),
[](const fs::directory_entry& a, const fs::directory_entry& b) {
return a.path().filename().string() < b.path().filename().string();
return a.path().filename().u8string() < b.path().filename().u8string();
});
for (const auto& entry : entries) {
std::string path = entry.path().string();
std::string ext = entry.path().extension().string();
std::string path = entry.path().u8string();
std::string ext = entry.path().extension().u8string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".webp") {
@@ -397,19 +398,19 @@ 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) {
fs::path path = cli_params->preview_path;
if (encoded_image_format_from_path(path.string()) == EncodedImageFormat::UNKNOWN)
fs::path path = fs::u8path(cli_params->preview_path);
if (encoded_image_format_from_path(path.u8string()) == EncodedImageFormat::UNKNOWN)
path += ".png";
if (std::regex_search(path.string(), format_specifier_regex))
path = fs::path(format_frame_idx(path.string(), continuous_preview_counter++));
if (!write_image_to_file(path.string(),
if (std::regex_search(path.u8string(), format_specifier_regex))
path = fs::u8path(format_frame_idx(path.u8string(), continuous_preview_counter++));
if (!write_image_to_file(path.u8string(),
image->data,
image->width,
image->height,
image->channel,
"",
cli_params->compression_quality)) {
LOG_ERROR("save preview image to '%s' failed", path.string().c_str());
LOG_ERROR("save preview image to '%s' failed", path.u8string().c_str());
}
} else {
int fps = cli_params->preview_method == PREVIEW_PROJ ? cli_params->preview_fps / 4 : cli_params->preview_fps;
@@ -420,12 +421,12 @@ void step_callback(int step, int frame_count, sd_image_t* image, bool is_noisy,
}
static fs::path get_video_audio_sidecar_path(const SDCliParams& cli_params) {
fs::path out_path = cli_params.output_path;
fs::path out_path = fs::u8path(cli_params.output_path);
fs::path base_path = out_path;
fs::path ext = out_path.has_extension() ? out_path.extension() : fs::path{};
std::string ext_lower = ext.string();
std::string ext_lower = ext.u8string();
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.u8string());
if (!ext.empty()) {
if (output_format == EncodedImageFormat::JPEG ||
output_format == EncodedImageFormat::PNG ||
@@ -450,14 +451,14 @@ bool save_results(const SDCliParams& cli_params,
}
namespace fs = std::filesystem;
fs::path out_path = cli_params.output_path;
fs::path out_path = fs::u8path(cli_params.output_path);
if (!out_path.parent_path().empty()) {
std::error_code ec;
fs::create_directories(out_path.parent_path(), ec);
if (ec) {
LOG_ERROR("failed to create directory '%s': %s",
out_path.parent_path().string().c_str(), ec.message().c_str());
out_path.parent_path().u8string().c_str(), ec.message().c_str());
return false;
}
}
@@ -465,9 +466,9 @@ bool save_results(const SDCliParams& cli_params,
fs::path base_path = out_path;
fs::path ext = out_path.has_extension() ? out_path.extension() : fs::path{};
std::string ext_lower = ext.string();
std::string ext_lower = ext.u8string();
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.string());
const EncodedImageFormat output_format = encoded_image_format_from_path(out_path.u8string());
if (!ext.empty()) {
if (output_format == EncodedImageFormat::JPEG ||
output_format == EncodedImageFormat::PNG ||
@@ -493,8 +494,8 @@ bool save_results(const SDCliParams& cli_params,
std::string params = gen_params.embed_image_metadata
? get_image_params(ctx_params, gen_params, metadata_seed, cli_params.mode)
: "";
const bool ok = write_image_to_file(path.string(), img.data, img.width, img.height, img.channel, params, cli_params.compression_quality);
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
const bool ok = write_image_to_file(path.u8string(), img.data, img.width, img.height, img.channel, params, cli_params.compression_quality);
LOG_INFO("save result image %d to '%s' (%s)", idx, path.u8string().c_str(), ok ? "success" : "failure");
return ok;
};
@@ -502,14 +503,14 @@ bool save_results(const SDCliParams& cli_params,
if (generated_audio == nullptr) {
return;
}
if (write_wav_to_file(wav_path.string(),
if (write_wav_to_file(wav_path.u8string(),
generated_audio->data,
generated_audio->sample_count,
generated_audio->channels,
generated_audio->sample_rate)) {
LOG_INFO("save result audio to '%s'", wav_path.string().c_str());
LOG_INFO("save result audio to '%s'", wav_path.u8string().c_str());
} else {
LOG_WARN("failed to save result audio to '%s'", wav_path.string().c_str());
LOG_WARN("failed to save result audio to '%s'", wav_path.u8string().c_str());
}
};
@@ -522,7 +523,7 @@ bool save_results(const SDCliParams& cli_params,
pattern += ext;
for (int i = 0; i < num_results; ++i) {
fs::path img_path = format_frame_idx(pattern.string(), output_begin_idx + i);
fs::path img_path = fs::u8path(format_frame_idx(pattern.u8string(), output_begin_idx + i));
if (write_image(img_path, i)) {
sucessful_reults++;
}
@@ -540,11 +541,11 @@ bool save_results(const SDCliParams& cli_params,
fs::path video_path = base_path;
video_path += ext;
std::string final_ext_lower = ext.string();
std::string final_ext_lower = ext.u8string();
std::transform(final_ext_lower.begin(), final_ext_lower.end(), final_ext_lower.begin(), ::tolower);
const bool mux_audio = generated_audio != nullptr && (final_ext_lower == ".avi" || final_ext_lower == ".webm");
if (create_video_from_sd_images(video_path.string().c_str(), results, num_results, gen_params.fps, cli_params.compression_quality, mux_audio ? generated_audio : nullptr, params) == 0) {
LOG_INFO("save result video to '%s'", video_path.string().c_str());
if (create_video_from_sd_images(video_path.u8string().c_str(), results, num_results, gen_params.fps, cli_params.compression_quality, mux_audio ? generated_audio : nullptr, params) == 0) {
LOG_INFO("save result video to '%s'", video_path.u8string().c_str());
if (generated_audio != nullptr && !mux_audio) {
fs::path wav_path = video_path;
wav_path.replace_extension(".wav");
@@ -552,7 +553,7 @@ bool save_results(const SDCliParams& cli_params,
}
return true;
} else {
LOG_ERROR("Failed to save result video to '%s'", video_path.string().c_str());
LOG_ERROR("Failed to save result video to '%s'", video_path.u8string().c_str());
return false;
}
}
@@ -701,7 +702,7 @@ int main(int argc, const char* argv[]) {
LOG_DEBUG("%s", gen_params.to_string().c_str());
if (!cli_params.imatrix_out.empty()) {
if (fs::exists(cli_params.imatrix_out) &&
if (fs::exists(fs::u8path(cli_params.imatrix_out)) &&
std::find(cli_params.imatrix_in.begin(), cli_params.imatrix_in.end(), cli_params.imatrix_out) == cli_params.imatrix_in.end()) {
LOG_WARN("imatrix file '%s' already exists and will be overwritten", cli_params.imatrix_out.c_str());
}
+38 -29
View File
@@ -75,7 +75,7 @@ static std::string utf16_to_utf8(const std::wstring& wstr) {
return utf8;
}
static std::string argv_to_utf8(int index, const char** argv) {
std::string argv_to_utf8(int index, const char** argv) {
(void)argv;
int argc;
wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc);
@@ -91,7 +91,7 @@ static std::string argv_to_utf8(int index, const char** argv) {
}
#else // Linux / macOS
static std::string argv_to_utf8(int index, const char** argv) {
std::string argv_to_utf8(int index, const char** argv) {
return std::string(argv[index]);
}
@@ -839,16 +839,17 @@ ArgOptions SDContextParams::get_options() {
void SDContextParams::build_embedding_map() {
static const std::vector<std::string> valid_ext = {".gguf", ".safetensors", ".pt", ".ckpt"};
if (!fs::exists(embedding_dir) || !fs::is_directory(embedding_dir)) {
const fs::path embedding_dir_path = fs::u8path(embedding_dir);
if (!fs::exists(embedding_dir_path) || !fs::is_directory(embedding_dir_path)) {
return;
}
for (auto& p : fs::directory_iterator(embedding_dir)) {
for (auto& p : fs::directory_iterator(embedding_dir_path)) {
if (!p.is_regular_file())
continue;
auto path = p.path();
std::string ext = path.extension().string();
std::string ext = path.extension().u8string();
bool valid = false;
for (auto& e : valid_ext) {
@@ -860,8 +861,8 @@ void SDContextParams::build_embedding_map() {
if (!valid)
continue;
std::string key = path.stem().string();
std::string value = path.string();
std::string key = path.stem().u8string();
std::string value = path.u8string();
embedding_map[key] = value;
}
@@ -1572,7 +1573,7 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
ref_image_paths.push_back(argv[index]);
ref_image_paths.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -1580,7 +1581,7 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
ref_video_paths.push_back(argv[index]);
ref_video_paths.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -1588,7 +1589,7 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
ref_video_audio_paths.push_back(argv[index]);
ref_video_audio_paths.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -1596,7 +1597,7 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
ref_audio_paths.push_back(argv[index]);
ref_audio_paths.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -1604,7 +1605,7 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
ref_audio_paths.push_back(argv[index]);
ref_audio_paths.push_back(argv_to_utf8(index, argv));
return 1;
};
@@ -1703,15 +1704,15 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
const char* arg = argv[index];
std::ifstream f(arg, std::ios::binary);
const std::string arg = argv_to_utf8(index, argv);
std::ifstream f(fs::u8path(arg), std::ios::binary);
try {
prompt = std::string(std::istreambuf_iterator<char>{f}, {});
} catch (const std::ios_base::failure&) {
f.setstate(std::ios_base::failbit);
}
if (f.fail()) {
LOG_ERROR("error: failed to read prompt file '%s'\n", arg);
LOG_ERROR("error: failed to read prompt file '%s'\n", arg.c_str());
return -1;
}
return 1;
@@ -1721,15 +1722,15 @@ ArgOptions SDGenerationParams::get_options() {
if (++index >= argc) {
return -1;
}
const char* arg = argv[index];
std::ifstream f(arg, std::ios::binary);
const std::string arg = argv_to_utf8(index, argv);
std::ifstream f(fs::u8path(arg), std::ios::binary);
try {
negative_prompt = std::string(std::istreambuf_iterator<char>{f}, {});
} catch (const std::ios_base::failure&) {
f.setstate(std::ios_base::failbit);
}
if (f.fail()) {
LOG_ERROR("error: failed to read negative prompt file '%s'\n", arg);
LOG_ERROR("error: failed to read negative prompt file '%s'\n", arg.c_str());
return -1;
}
return 1;
@@ -2050,22 +2051,22 @@ static bool resolve_model_file_from_dir(const std::string& model_name,
if (model_name.empty() ||
model_name.find('/') != std::string::npos ||
model_name.find('\\') != std::string::npos ||
fs::path(model_name).has_root_path() ||
fs::u8path(model_name).has_root_path() ||
ends_with_valid_ext()) {
LOG_ERROR("%s must be a model name without path or extension: %s", label, model_name.c_str());
return false;
}
fs::path model_dir_path = model_dir;
fs::path model_dir_path = fs::u8path(model_dir);
for (const auto& ext : valid_ext) {
fs::path try_path = model_dir_path / (model_name + ext);
fs::path try_path = model_dir_path / fs::u8path(model_name + ext);
if (fs::exists(try_path) && fs::is_regular_file(try_path)) {
resolved_path = try_path.lexically_normal().string();
resolved_path = try_path.lexically_normal().u8string();
return true;
}
}
LOG_ERROR("can not find %s %s in %s", label, model_name.c_str(), model_dir_path.lexically_normal().string().c_str());
LOG_ERROR("can not find %s %s in %s", label, model_name.c_str(), model_dir_path.lexically_normal().u8string().c_str());
return false;
}
@@ -2359,10 +2360,18 @@ bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
}
fs::path final_path;
if (is_absolute_path(raw_path)) {
final_path = raw_path;
} else {
final_path = fs::path(lora_model_dir) / raw_path;
try {
if (is_absolute_path(raw_path)) {
final_path = fs::u8path(raw_path);
} else {
final_path = fs::u8path(lora_model_dir) / fs::u8path(raw_path);
}
} catch (const std::exception&) { // not UTF-8, e.g. a prompt file in another encoding
LOG_WARN("invalid lora path %s", raw_path.c_str());
tmp = m.suffix().str();
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
missing = true;
continue;
}
if (!fs::exists(final_path)) {
bool found = false;
@@ -2376,7 +2385,7 @@ bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
}
}
if (!found) {
LOG_WARN("can not found lora %s", final_path.lexically_normal().string().c_str());
LOG_WARN("can not found lora %s", final_path.lexically_normal().u8string().c_str());
tmp = m.suffix().str();
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
missing = true;
@@ -2384,7 +2393,7 @@ bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
}
}
const std::string key = final_path.lexically_normal().string();
const std::string key = final_path.lexically_normal().u8string();
if (is_high_noise)
high_noise_lora_map[key] += mul;
+4
View File
@@ -12,6 +12,10 @@
#include "resource_owners.hpp"
#include "stable-diffusion.h"
// The command-line argument at `index` as UTF-8 (on Windows, read again from
// the UTF-16 command line: argv itself is in the ANSI code page).
std::string argv_to_utf8(int index, const char** argv);
#define SAFE_STR(s) ((s) ? (s) : "")
#define BOOL_STR(b) ((b) ? "true" : "false")
+7 -5
View File
@@ -15,10 +15,12 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#define STBI_WINDOWS_UTF8
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#define STBIW_WINDOWS_UTF8
#include "stb_image_write.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
@@ -147,7 +149,7 @@ private:
#endif
bool read_binary_file_bytes(const char* path, std::vector<uint8_t>& data) {
std::ifstream fin(fs::path(path), std::ios::binary);
std::ifstream fin(fs::u8path(path), std::ios::binary);
if (!fin) {
return false;
}
@@ -170,7 +172,7 @@ bool read_binary_file_bytes(const char* path, std::vector<uint8_t>& data) {
}
bool write_binary_file_bytes(const std::string& path, const std::vector<uint8_t>& data) {
std::ofstream fout(fs::path(path), std::ios::binary);
std::ofstream fout(fs::u8path(path), std::ios::binary);
if (!fout) {
return false;
}
@@ -714,7 +716,7 @@ static std::pair<uint64_t, uint64_t> audio_sample_range_for_video_frame(const sd
}
EncodedImageFormat encoded_image_format_from_path(const std::string& path) {
std::string ext = fs::path(path).extension().string();
std::string ext = fs::u8path(path).extension().u8string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == ".jpg" || ext == ".jpeg" || ext == ".jpe") {
@@ -1413,7 +1415,7 @@ bool write_wav_to_file(const std::string& path,
return false;
}
std::ofstream file(path, std::ios::binary);
std::ofstream file(fs::u8path(path), std::ios::binary);
if (!file.is_open()) {
return false;
}
@@ -1475,7 +1477,7 @@ bool load_wav_from_file(const std::string& path,
sample_rate = 0;
channels = 0;
std::ifstream file(path, std::ios::binary);
std::ifstream file(fs::u8path(path), std::ios::binary);
uint8_t riff_header[12];
if (!file.read(reinterpret_cast<char*>(riff_header), sizeof(riff_header)) ||
std::memcmp(riff_header, "RIFF", 4) != 0 ||
+2 -1
View File
@@ -1,5 +1,6 @@
#include "routes.h"
#include <filesystem>
#include <fstream>
#include <iterator>
@@ -7,7 +8,7 @@ void register_index_endpoints(httplib::Server& svr, const SDSvrParams& svr_param
const std::string serve_html_path = svr_params.serve_html_path;
svr.Get("/", [serve_html_path, index_html](const httplib::Request&, httplib::Response& res) {
if (!serve_html_path.empty()) {
std::ifstream file(serve_html_path);
std::ifstream file(std::filesystem::u8path(serve_html_path));
if (file) {
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
res.set_content(content, "text/html");
+6 -6
View File
@@ -28,10 +28,10 @@ static std::string extract_and_remove_sd_cpp_extra_args(std::string& text) {
static fs::path resolve_display_model_path(const ServerRuntime& runtime) {
const auto& ctx = *runtime.ctx_params;
if (!ctx.model_path.empty()) {
return fs::path(ctx.model_path);
return fs::u8path(ctx.model_path);
}
if (!ctx.diffusion_model_path.empty()) {
return fs::path(ctx.diffusion_model_path);
return fs::u8path(ctx.diffusion_model_path);
}
return {};
}
@@ -583,9 +583,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
svr.Get("/sdapi/v1/sd-models", [runtime](const httplib::Request&, httplib::Response& res) {
fs::path model_path = resolve_display_model_path(*runtime);
json entry;
entry["title"] = model_path.stem();
entry["model_name"] = model_path.stem();
entry["filename"] = model_path.filename();
entry["title"] = model_path.stem().u8string();
entry["model_name"] = model_path.stem().u8string();
entry["filename"] = model_path.filename().u8string();
entry["hash"] = "8888888888";
entry["sha256"] = "8888888888888888888888888888888888888888888888888888888888888888";
entry["config"] = nullptr;
@@ -598,7 +598,7 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
fs::path model_path = resolve_display_model_path(*runtime);
json r;
r["samples_format"] = "png";
r["sd_model_checkpoint"] = model_path.stem();
r["sd_model_checkpoint"] = model_path.stem().u8string();
res.set_content(r.dump(), "application/json");
});
+2 -2
View File
@@ -90,10 +90,10 @@ static json make_vae_tiling_json(const sd_tiling_params_t& params) {
static fs::path resolve_display_model_path(const ServerRuntime& runtime) {
const auto& ctx = *runtime.ctx_params;
if (!ctx.model_path.empty()) {
return fs::path(ctx.model_path);
return fs::u8path(ctx.model_path);
}
if (!ctx.diffusion_model_path.empty()) {
return fs::path(ctx.diffusion_model_path);
return fs::u8path(ctx.diffusion_model_path);
}
return {};
}
+8 -8
View File
@@ -22,7 +22,7 @@ static std::string lower_ascii(std::string value) {
}
static bool is_supported_model_ext(const fs::path& p) {
auto ext = lower_ascii(p.extension().string());
auto ext = lower_ascii(p.extension().u8string());
return ext == ".gguf" || ext == ".pt" || ext == ".pth" || ext == ".safetensors";
}
@@ -226,7 +226,7 @@ bool SDSvrParams::validate() {
return false;
}
if (!serve_html_path.empty() && !fs::exists(serve_html_path)) {
if (!serve_html_path.empty() && !fs::exists(fs::u8path(serve_html_path))) {
LOG_ERROR("error: serve_html_path file does not exist: %s", serve_html_path.c_str());
return false;
}
@@ -260,7 +260,7 @@ void refresh_lora_cache(ServerRuntime& rt) {
return;
}
fs::path lora_dir = rt.ctx_params->lora_model_dir;
fs::path lora_dir = fs::u8path(rt.ctx_params->lora_model_dir);
std::error_code ec;
if (fs::exists(lora_dir, ec) && !ec && fs::is_directory(lora_dir, ec) && !ec) {
try {
@@ -293,13 +293,13 @@ void refresh_lora_cache(ServerRuntime& rt) {
it.increment(ec);
}
} catch (const std::exception& e) {
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), e.what());
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.u8string().c_str(), e.what());
return;
}
}
if (ec) {
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.string().c_str(), ec.message().c_str());
LOG_WARN("error while scanning lora directory '%s': %s", lora_dir.u8string().c_str(), ec.message().c_str());
return;
}
@@ -328,7 +328,7 @@ void refresh_upscaler_cache(ServerRuntime& rt) {
previous_cache = *rt.upscaler_cache;
}
fs::path upscaler_dir = rt.ctx_params->hires_upscalers_dir;
fs::path upscaler_dir = fs::u8path(rt.ctx_params->hires_upscalers_dir);
std::error_code ec;
if (fs::exists(upscaler_dir, ec) && !ec && fs::is_directory(upscaler_dir, ec) && !ec) {
try {
@@ -384,13 +384,13 @@ void refresh_upscaler_cache(ServerRuntime& rt) {
it.increment(ec);
}
} catch (const std::exception& e) {
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), e.what());
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.u8string().c_str(), e.what());
return;
}
}
if (ec) {
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.string().c_str(), ec.message().c_str());
LOG_WARN("error while scanning upscalers directory '%s': %s", upscaler_dir.u8string().c_str(), ec.message().c_str());
return;
}
+24 -1
View File
@@ -2006,6 +2006,7 @@ struct LLMEmbedder : public Conditioner {
} else if (sd_version_is_pid(version)) {
arch = LLM::LLMArch::GEMMA2_2B;
} else if (version == VERSION_QWEN_IMAGE_2_1 ||
sd_version_is_flux3(version) ||
sd_version_is_lingbot_video(version) ||
sd_version_is_ideogram4(version) ||
sd_version_is_boogu_image(version) ||
@@ -2201,6 +2202,9 @@ struct LLMEmbedder : public Conditioner {
weights.insert(weights.end(), suffix_tokens.size(), 1.f);
}
if (sd_version_is_flux3(version)) {
min_length = std::min(max_length, ((tokens.size() + 79) / 80) * 80);
}
std::vector<float> mask;
tokenizer->pad_tokens(tokens, &weights, &mask, min_length, max_length);
@@ -2254,6 +2258,15 @@ struct LLMEmbedder : public Conditioner {
}
}
sd::Tensor<int32_t> position_ids;
if (sd_version_is_flux3(version)) {
position_ids = sd::Tensor<int32_t>::zeros({static_cast<int64_t>(tokens.size() * 4)});
for (size_t axis = 0; axis < 4; ++axis) {
for (size_t i = 0; i < tokens.size(); ++i) {
position_ids[axis * tokens.size() + i] = mask[i] == 0.f ? 1 : static_cast<int32_t>(i);
}
}
}
auto hidden_states = llm->compute(n_threads,
input_ids,
attention_mask,
@@ -2262,7 +2275,8 @@ struct LLMEmbedder : public Conditioner {
false,
false,
deepstack_image_embeds,
image_grids);
image_grids,
position_ids);
if (hidden_states.empty()) {
LOG_ERROR("LLM prompt encoding failed");
return {};
@@ -3025,6 +3039,15 @@ struct LLMEmbedder : public Conditioner {
prompt += "<|im_end|>\n<|im_start|>assistant\n";
}
} else if (sd_version_is_flux3(version)) {
prompt_template_encode_start_idx = 0;
max_length = 4096;
out_layers = {4, 8, 12, 16, 20, 24, 28, 32};
prompt = "<|im_start|>user\n";
prompt_attn_range.first = static_cast<int>(prompt.size());
prompt += conditioner_params.text;
prompt_attn_range.second = static_cast<int>(prompt.size());
prompt += "<|im_end|>\n<|im_start|>assistant\n";
} else if (version == VERSION_FLUX2_KLEIN) {
prompt_template_encode_start_idx = 0;
min_length = 512;
+3 -2
View File
@@ -3,6 +3,7 @@
#include <cstdint>
#include <cstring>
#include <exception>
#include <filesystem>
#include <fstream>
#include <map>
#include <memory>
@@ -108,7 +109,7 @@ static bool preallocate_output_file(const std::string& output_path, uint64_t fil
return true;
}
std::fstream file(output_path, std::ios::binary | std::ios::in | std::ios::out);
std::fstream file(std::filesystem::u8path(output_path), std::ios::binary | std::ios::in | std::ios::out);
if (!file.is_open()) {
if (error != nullptr) {
*error = "failed to open output file '" + output_path + "' for preallocation";
@@ -251,7 +252,7 @@ static bool stream_tensor_data(ModelLoader& model_loader,
for (int worker = 0; worker < n_threads; worker++) {
workers.emplace_back([&]() {
std::fstream output_file(output_path, std::ios::binary | std::ios::in | std::ios::out);
std::fstream output_file(std::filesystem::u8path(output_path), std::ios::binary | std::ios::in | std::ios::out);
if (!output_file.is_open()) {
fail("failed to open output file '" + output_path + "' for tensor writing");
return;
+2 -1
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include "core/ggml_extend_backend.h"
@@ -100,7 +101,7 @@ void ggml_ext_tensor_diff(
}
ggml_tensor* load_tensor_from_file(ggml_context* ctx, const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(std::filesystem::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
LOG_ERROR("failed to open '%s'", file_path.c_str());
return nullptr;
+2 -1
View File
@@ -3,6 +3,7 @@
#include <array>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
@@ -105,7 +106,7 @@ namespace sd {
template <typename T>
inline Tensor<T> load_tensor_from_file_as_tensor(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(std::filesystem::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("failed to open tensor file: " + file_path);
}
+5 -4
View File
@@ -7,6 +7,7 @@
#include <cstdlib>
#include <cstring>
#include <exception>
#include <filesystem>
#include <fstream>
#include <locale>
#include <regex>
@@ -123,12 +124,12 @@ int round_up_to(int value, int base) {
#include <windows.h>
bool file_exists(const std::string& filename) {
DWORD attributes = GetFileAttributesA(filename.c_str());
DWORD attributes = GetFileAttributesW(std::filesystem::u8path(filename).c_str());
return (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY));
}
bool is_directory(const std::string& path) {
DWORD attributes = GetFileAttributesA(path.c_str());
DWORD attributes = GetFileAttributesW(std::filesystem::u8path(path).c_str());
return (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY));
}
@@ -152,8 +153,8 @@ std::unique_ptr<MmapWrapper> MmapWrapper::create(const std::string& filename, bo
void* mapped_data = nullptr;
size_t file_size = 0;
HANDLE file_handle = CreateFileA(
filename.c_str(),
HANDLE file_handle = CreateFileW(
std::filesystem::u8path(filename).c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
+13 -1
View File
@@ -66,6 +66,7 @@ enum SDVersion {
VERSION_MING_IMAGE,
VERSION_Z_IMAGE_L2P,
VERSION_IRIS,
VERSION_FLUX3,
VERSION_COUNT,
};
@@ -118,6 +119,10 @@ static inline bool sd_version_is_flux(SDVersion version) {
return false;
}
static inline bool sd_version_is_flux3(SDVersion version) {
return version == VERSION_FLUX3;
}
static inline bool sd_version_is_flux2(SDVersion version) {
if (version == VERSION_FLUX2 || version == VERSION_FLUX2_KLEIN) {
return true;
@@ -280,7 +285,13 @@ static inline bool sd_version_uses_flux_vae(SDVersion version) {
}
static inline bool sd_version_uses_flux2_vae(SDVersion version) {
if (sd_version_is_flux2(version) || sd_version_is_ernie_image(version) || sd_version_is_lens(version) || sd_version_is_ideogram4(version) || sd_version_is_sefi_image(version) || sd_version_is_llada_image(version)) {
if (sd_version_is_flux2(version) ||
sd_version_is_flux3(version) ||
sd_version_is_ernie_image(version) ||
sd_version_is_lens(version) ||
sd_version_is_ideogram4(version) ||
sd_version_is_sefi_image(version) ||
sd_version_is_llada_image(version)) {
return true;
}
return false;
@@ -311,6 +322,7 @@ static inline bool sd_version_is_inpaint(SDVersion version) {
static inline bool sd_version_is_dit(SDVersion version) {
if (sd_version_is_flux(version) ||
sd_version_is_flux2(version) ||
sd_version_is_flux3(version) ||
sd_version_is_ltxav(version) ||
sd_version_is_minimax_h3(version) ||
sd_version_is_sd3(version) ||
+256
View File
@@ -0,0 +1,256 @@
#ifndef __SD_MODEL_DIFFUSION_FLUX3_HPP__
#define __SD_MODEL_DIFFUSION_FLUX3_HPP__
#include "model/diffusion/flux.hpp"
namespace Flux3 {
struct Flux3Config {
int64_t hidden_size = 3072;
int64_t context_dim = 20480;
int64_t channels = 128;
int64_t mlp_dim = 9216;
int heads = 24;
int depth = 5;
int single_depth = 28;
bool flow_map = false;
static Flux3Config detect_from_weights(const String2TensorStorage& tensors, const std::string& prefix) {
Flux3Config config;
int depth = 0, single_depth = 0;
bool detected = false;
for (const auto& [name, tensor] : tensors) {
if (!starts_with(name, prefix + ".")) {
continue;
}
std::string key = name.substr(prefix.size() + 1);
if (key == "txt_in.weight") {
detected = true;
config.context_dim = tensor.ne[0];
config.hidden_size = tensor.ne[1];
} else if (key == "emb_in.image.weight") {
config.channels = tensor.ne[0];
} else if (key == "single_blocks.0.mlp_out.weight") {
config.mlp_dim = tensor.ne[0];
} else if (key == "ref_time_in.in_layer.weight") {
config.flow_map = true;
}
if (starts_with(key, "txt_mode_blocks.")) {
depth = std::max(depth, atoi(key.substr(16).c_str()) + 1);
}
if (starts_with(key, "single_blocks.")) {
single_depth = std::max(single_depth, atoi(key.substr(14).c_str()) + 1);
}
}
if (depth > 0)
config.depth = depth;
if (single_depth > 0)
config.single_depth = single_depth;
config.heads = static_cast<int>(config.hidden_size / 128);
if (detected) {
LOG_VERBOSE("flux3: hidden_size = %" PRId64 ", context_dim = %" PRId64 ", depth = %d, single_depth = %d, flow_map = %s",
config.hidden_size, config.context_dim, config.depth, config.single_depth, config.flow_map ? "true" : "false");
}
return config;
}
};
static ggml_tensor* token_slice(ggml_context* ctx, ggml_tensor* x, int64_t start, int64_t length) {
return ggml_view_3d(ctx, x, x->ne[0], length, x->ne[2], x->nb[1], x->nb[2], start * x->nb[1]);
}
struct Block : public GGMLBlock {
int heads;
Block(const Flux3Config& config)
: heads(config.heads) {
for (const auto* name : {"q_proj", "k_proj", "v_proj", "attn_out"}) {
blocks[name] = std::make_shared<Linear>(config.hidden_size, config.hidden_size, false);
}
blocks["mlp_in"] = std::make_shared<Linear>(config.hidden_size, 2 * config.mlp_dim, false);
blocks["mlp_out"] = std::make_shared<Linear>(config.mlp_dim, config.hidden_size, false);
blocks["norm"] = std::make_shared<Flux::QKNorm>(config.hidden_size / heads);
}
ggml_tensor* linear(GGMLRunnerContext* ctx, const std::string& name, ggml_tensor* x) {
return std::dynamic_pointer_cast<Linear>(blocks[name])->forward(ctx, x);
}
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* pe, const std::vector<Flux::ModulationOut>& mods, const std::vector<int64_t>& lengths) {
auto gc = ctx->ggml_ctx;
ggml_tensor* x_mod = nullptr;
int64_t offset = 0;
for (size_t i = 0; i < lengths.size(); ++i) {
auto part = token_slice(gc, x, offset, lengths[i]);
part = Flux::modulate(gc, ggml_norm(gc, part, 1e-6f), mods[i].shift, mods[i].scale);
x_mod = x_mod ? ggml_concat(gc, x_mod, part, 1) : part;
offset += lengths[i];
}
auto split = [&](ggml_tensor* v) {
return ggml_reshape_4d(gc, v, x->ne[0] / heads, heads, x->ne[1], x->ne[2]);
};
auto norm = std::dynamic_pointer_cast<Flux::QKNorm>(blocks["norm"]);
auto q = norm->query_norm(ctx, split(linear(ctx, "q_proj", x_mod)));
auto k = norm->key_norm(ctx, split(linear(ctx, "k_proj", x_mod)));
auto v = split(linear(ctx, "v_proj", x_mod));
auto attn = Rope::attention(ctx, q, k, v, pe, nullptr);
auto mlp = ggml_ext_silu_act(gc, linear(ctx, "mlp_in", x_mod));
auto update = ggml_add(gc, linear(ctx, "attn_out", attn), linear(ctx, "mlp_out", mlp));
ggml_tensor* out = nullptr;
offset = 0;
for (size_t i = 0; i < lengths.size(); ++i) {
auto part = ggml_mul(gc, token_slice(gc, update, offset, lengths[i]), mods[i].gate);
part = ggml_add(gc, token_slice(gc, x, offset, lengths[i]), part);
out = out ? ggml_concat(gc, out, part, 1) : part;
offset += lengths[i];
}
return out;
}
};
struct Model : public GGMLBlock {
Flux3Config config;
Model(const Flux3Config& config)
: config(config) {
blocks["txt_in"] = std::make_shared<Linear>(config.context_dim, config.hidden_size, false);
blocks["time_in"] = std::make_shared<Flux::MLPEmbedder>(256, config.hidden_size, false);
if (config.flow_map) {
blocks["ref_time_in"] = std::make_shared<Flux::MLPEmbedder>(256, config.hidden_size, false);
}
for (const std::string mode : {"image", "image_cond", "txt"}) {
blocks["early_stream_modulations." + mode] = std::make_shared<Flux::Modulation>(config.hidden_size, false, false);
blocks["single_stream_modulations." + mode] = std::make_shared<Flux::Modulation>(config.hidden_size, false, false);
const std::string base = mode == "txt" ? "txt_mode_blocks." : "content_mode_blocks." + mode + ".";
for (int i = 0; i < config.depth; ++i) {
blocks[base + std::to_string(i)] = std::make_shared<Block>(config);
}
if (mode != "txt") {
blocks["emb_in." + mode] = std::make_shared<Linear>(config.channels, config.hidden_size, false);
}
}
for (int i = 0; i < config.single_depth; ++i) {
blocks["single_blocks." + std::to_string(i)] = std::make_shared<Block>(config);
}
blocks["final_layer.image"] = std::make_shared<Flux::LastLayer>(config.hidden_size, 1, config.channels, false, false);
}
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* image, ggml_tensor* text, ggml_tensor* reference, ggml_tensor* timestep, ggml_tensor* zero, ggml_tensor* target, ggml_tensor* pe) {
auto gc = ctx->ggml_ctx;
const int64_t width = image->ne[0], height = image->ne[1];
auto time_in = std::dynamic_pointer_cast<Flux::MLPEmbedder>(blocks["time_in"]);
auto image_vec = time_in->forward(ctx, ggml_ext_timestep_embedding(gc, timestep, 256, 10000, 1000.f));
auto clean_vec = time_in->forward(ctx, ggml_ext_timestep_embedding(gc, zero, 256, 10000, 1000.f));
if (config.flow_map) {
auto ref_time = std::dynamic_pointer_cast<Flux::MLPEmbedder>(blocks["ref_time_in"]);
auto ref_vec = ref_time->forward(ctx, ggml_ext_timestep_embedding(gc, target, 256, 10000, 1000.f));
image_vec = ggml_add(gc, image_vec, ref_vec);
clean_vec = ggml_add(gc, clean_vec, ref_vec);
}
std::vector<std::string> modes{"image"};
std::vector<ggml_tensor*> inputs{DiT::patchify(gc, image, 1, 1)};
if (reference) {
modes.push_back("image_cond");
inputs.push_back(reference);
}
modes.push_back("txt");
inputs.push_back(text);
std::vector<Flux::ModulationOut> single_mods;
std::vector<int64_t> lengths;
ggml_tensor* joint = nullptr;
int64_t offset = 0;
for (size_t m = 0; m < modes.size(); ++m) {
const auto& mode = modes[m];
auto vec = m == 0 ? image_vec : clean_vec;
auto early = std::dynamic_pointer_cast<Flux::Modulation>(blocks["early_stream_modulations." + mode])->forward(ctx, vec)[0];
single_mods.push_back(std::dynamic_pointer_cast<Flux::Modulation>(blocks["single_stream_modulations." + mode])->forward(ctx, vec)[0]);
lengths.push_back(inputs[m]->ne[1]);
auto h = std::dynamic_pointer_cast<Linear>(blocks[mode == "txt" ? "txt_in" : "emb_in." + mode])->forward(ctx, inputs[m]);
auto mode_pe = ggml_view_4d(gc, pe, 2, 2, 64, lengths.back(), pe->nb[1], pe->nb[2], pe->nb[3], offset * pe->nb[3]);
const std::string base = mode == "txt" ? "txt_mode_blocks." : "content_mode_blocks." + mode + ".";
for (int i = 0; i < config.depth; ++i) {
h = std::dynamic_pointer_cast<Block>(blocks[base + std::to_string(i)])->forward(ctx, h, mode_pe, {early}, {lengths.back()});
}
joint = joint ? ggml_concat(gc, joint, h, 1) : h;
offset += lengths.back();
}
for (int i = 0; i < config.single_depth; ++i) {
joint = std::dynamic_pointer_cast<Block>(blocks["single_blocks." + std::to_string(i)])->forward(ctx, joint, pe, single_mods, lengths);
}
auto out = std::dynamic_pointer_cast<Flux::LastLayer>(blocks["final_layer.image"])->forward(ctx, token_slice(gc, joint, 0, lengths[0]), image_vec);
return DiT::unpatchify(gc, out, height, width, 1, 1);
}
};
struct Flux3Runner : public DiffusionModelRunner {
Flux3Config config;
Model model;
std::vector<float> pe_values;
sd::Tensor<float> zero = sd::Tensor<float>::zeros({1});
Flux3Runner(ggml_backend_t backend, const String2TensorStorage& tensors, const std::string& prefix, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
: DiffusionModelRunner(backend, prefix, weight_manager),
config(Flux3Config::detect_from_weights(tensors, prefix)),
model(config) {
model.init(params_ctx, tensors, prefix);
}
std::string get_desc() override { return "flux3"; }
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
model.get_param_tensors(tensors, prefix);
}
sd::Tensor<float> compute(int n_threads, const DiffusionParams& params) override {
if (params.ref_latents && params.ref_image_params.pass_to_dit && params.ref_latents->size() > 10) {
LOG_ERROR("FLUX.3 supports at most 10 reference images");
return {};
}
const auto* extra = diffusion_extra_as<Flux3DiffusionExtra>(params);
GGML_ASSERT(params.x && params.context && params.timesteps);
GGML_ASSERT(!config.flow_map || extra->target_timestep);
auto get_graph = [&]() {
auto gf = new_graph_custom(FLUX_GRAPH_SIZE);
auto x = make_input(*params.x);
auto text = make_input(*params.context);
GGML_ASSERT(x->ne[3] == 1 && text->ne[2] == 1);
Rope::Embedding embedding;
auto append_image = [&](int64_t w, int64_t h, float index) {
embedding.positions.append_image(static_cast<int>(h), static_cast<int>(w), 1, 0.5f, 0.5f);
for (int64_t y = 0; y < h; ++y) {
for (int64_t x = 0; x < w; ++x) {
embedding.ids.push_back({0.f, y * 0.5f, x * 0.5f, index});
}
}
};
append_image(x->ne[0], x->ne[1], 0.f);
ggml_tensor* references = nullptr;
if (params.ref_latents && params.ref_image_params.pass_to_dit) {
int index = 0;
for (const auto& ref : *params.ref_latents) {
auto r = make_input(ref);
append_image(r->ne[0], r->ne[1], -float(++index));
r = DiT::patchify(compute_ctx, r, 1, 1);
references = references ? ggml_concat(compute_ctx, references, r, 1) : r;
}
}
for (int64_t i = 0; i < text->ne[1]; ++i) {
embedding.ids.push_back({0.f, 0.f, 0.f, static_cast<float>(i)});
}
embedding.positions.append_tokens(text->ne[1]);
embedding.values = Rope::embed_nd(embedding.ids, 1, 10000.f, {32, 32, 32, 32}, embedding.layout, &embedding.frequencies);
pe_values = finish_rope_pe(std::move(embedding));
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, 64, pe_values.size() / 256);
set_backend_tensor_data(pe, pe_values.data());
auto runner_ctx = get_context();
auto out = model.forward(&runner_ctx, x, text, references, make_input(*params.timesteps), make_input(zero),
config.flow_map ? make_input(*extra->target_timestep) : nullptr, pe);
ggml_build_forward_expand(gf, out);
return gf;
};
return restore_trailing_singleton_dims(GGMLRunner::compute(get_graph, n_threads, false), params.x->dim());
}
};
} // namespace Flux3
#endif // __SD_MODEL_DIFFUSION_FLUX3_HPP__
+5
View File
@@ -64,6 +64,10 @@ struct FluxDiffusionExtra {
float pulid_id_weight = 1.0f;
};
struct Flux3DiffusionExtra {
const sd::Tensor<float>* target_timestep = nullptr;
};
struct AnimaDiffusionExtra {
const sd::Tensor<int32_t>* t5_ids = nullptr;
const sd::Tensor<float>* t5_weights = nullptr;
@@ -149,6 +153,7 @@ using DiffusionExtraParams = std::variant<std::monostate,
UNetDiffusionExtra,
SkipLayerDiffusionExtra,
FluxDiffusionExtra,
Flux3DiffusionExtra,
AnimaDiffusionExtra,
QwenImage21DiffusionExtra,
WanDiffusionExtra,
+10 -3
View File
@@ -2338,7 +2338,8 @@ namespace LLM {
const DeepStackImageEmbeds& deepstack_image_embeds_tensor,
const std::vector<ImageGrid>& image_grids,
std::set<int> out_layers,
bool return_all_hidden_states = false) {
bool return_all_hidden_states = false,
const sd::Tensor<int32_t>& position_ids = {}) {
ggml_cgraph* gf = new_graph_custom(LLM_GRAPH_SIZE);
ggml_tensor* input_ids = make_input(input_ids_tensor);
std::vector<std::pair<int, ggml_tensor*>> image_embeds;
@@ -2402,6 +2403,10 @@ namespace LLM {
}
}
if (!position_ids.empty()) {
GGML_ASSERT(position_ids.numel() == static_cast<int64_t>(input_pos_vec.size()));
input_pos_vec = position_ids.values();
}
auto input_pos = ggml_new_tensor_1d(compute_ctx,
GGML_TYPE_I32,
input_pos_vec.size());
@@ -2475,7 +2480,8 @@ namespace LLM {
bool return_all_hidden_states = false,
bool auto_runner_end = true,
const DeepStackImageEmbeds& deepstack_image_embeds = {},
const std::vector<ImageGrid>& image_grids = {}) {
const std::vector<ImageGrid>& image_grids = {},
const sd::Tensor<int32_t>& position_ids = {}) {
auto get_graph = [&]() -> ggml_cgraph* {
return build_graph(input_ids,
attention_mask,
@@ -2483,7 +2489,8 @@ namespace LLM {
deepstack_image_embeds,
image_grids,
out_layers,
return_all_hidden_states);
return_all_hidden_states,
position_ids);
};
return restore_trailing_singleton_dims(GGMLRunner::compute(get_graph, n_threads, auto_runner_end),
input_ids.dim() + 1);
+3 -2
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <ostream>
#include <string>
@@ -19,7 +20,7 @@ static void set_error(std::string* error, const std::string& message) {
}
bool is_gguf_file(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(std::filesystem::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
return false;
}
@@ -178,7 +179,7 @@ bool GGUFStreamingWriter::write_metadata(const std::string& file_path,
}
LOG_INFO("trying to save tensors to %s", file_path.c_str());
FILE* file = fopen(file_path.c_str(), "wb+");
FILE* file = ggml_fopen(file_path.c_str(), "wb+");
if (file == nullptr) {
set_error(error, "failed to open output file '" + file_path + "'");
close();
+2 -1
View File
@@ -2,6 +2,7 @@
#define __SD_MODEL_IO_GGUF_READER_EXT_H__
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
@@ -184,7 +185,7 @@ private:
public:
bool load(const std::string& file_path) {
std::ifstream fin(file_path, std::ios::binary | std::ios::ate);
std::ifstream fin(std::filesystem::u8path(file_path), std::ios::binary | std::ios::ate);
if (!fin) {
LOG_ERROR("failed to open '%s'", file_path.c_str());
return false;
+8 -8
View File
@@ -26,15 +26,15 @@ static void set_error(std::string* error, const std::string& message) {
}
static std::string resolve_index_shard_path(const std::string& index_path, const std::string& shard_path) {
fs::path shard_fs_path(shard_path);
fs::path shard_fs_path = fs::u8path(shard_path);
if (shard_fs_path.is_absolute()) {
return shard_fs_path.lexically_normal().string();
return shard_fs_path.lexically_normal().u8string();
}
return (fs::path(index_path).parent_path() / shard_fs_path).lexically_normal().string();
return (fs::u8path(index_path).parent_path() / shard_fs_path).lexically_normal().u8string();
}
bool is_safetensors_file(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(fs::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
return false;
}
@@ -153,7 +153,7 @@ bool read_safetensors_file(const std::string& file_path,
std::string* error,
std::map<std::string, std::string>* metadata,
std::set<std::string>* tensor_names) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(fs::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
set_error(error, "failed to open '" + file_path + "'");
return false;
@@ -418,7 +418,7 @@ bool read_safetensors_index_file(const std::string& file_path,
std::string* error) {
shard_paths.clear();
std::ifstream file(file_path);
std::ifstream file(fs::u8path(file_path));
if (!file.is_open()) {
set_error(error, "failed to open '" + file_path + "'");
return false;
@@ -522,7 +522,7 @@ bool write_safetensors_file(const std::string& file_path,
const std::string header_str = header.dump();
std::ofstream file(file_path, std::ios::binary);
std::ofstream file(fs::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
set_error(error, "failed to open '" + file_path + "' for writing");
return false;
@@ -596,7 +596,7 @@ bool SafetensorsStreamingWriter::write_metadata(const std::string& file_path,
data_start_ = ST_HEADER_SIZE_LEN + header_str.size();
LOG_INFO("trying to save tensors to %s", file_path.c_str());
std::ofstream file(file_path, std::ios::binary | std::ios::trunc);
std::ofstream file(fs::u8path(file_path), std::ios::binary | std::ios::trunc);
if (!file.is_open()) {
set_error(error, "failed to open '" + file_path + "' for writing");
return false;
+2 -1
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#include <unordered_map>
@@ -98,7 +99,7 @@ static std::string torch_legacy_diagnostics(const std::string& file_path, const
bool read_torch_legacy_file(const std::string& file_path,
std::vector<TensorStorage>& tensor_storages,
std::string* error) {
std::ifstream file(file_path, std::ios::binary);
std::ifstream file(std::filesystem::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
set_error(error, "failed to open '" + file_path + "'");
return false;
+8 -2
View File
@@ -4,6 +4,7 @@
#include <cinttypes>
#include <cstdarg>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <functional>
#include <mutex>
@@ -333,7 +334,7 @@ bool ModelLoader::init_from_safetensors_index_file(const std::string& file_path,
}
}
std::ifstream index_file(file_path);
std::ifstream index_file(std::filesystem::u8path(file_path));
const auto index = nlohmann::json::parse(index_file);
for (const auto& entry : index.at("weight_map").items()) {
const auto expected = (std::filesystem::u8path(file_path).parent_path() /
@@ -438,6 +439,11 @@ bool ModelLoader::init_from_diffusers_file(const std::string& file_path, const s
SDVersion ModelLoader::get_sd_version() const {
TensorStorage token_embedding_weight, input_block_weight, context_ebedding_weight;
if (tensor_storage_map.find("model.diffusion_model.emb_in.image.weight") != tensor_storage_map.end() &&
tensor_storage_map.find("model.diffusion_model.txt_mode_blocks.0.q_proj.weight") != tensor_storage_map.end()) {
return VERSION_FLUX3;
}
bool has_multiple_encoders = false;
bool is_unet = false;
@@ -1145,7 +1151,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb,
return;
}
} else if (!mmapped) {
file.open(file_path, std::ios::binary);
file.open(std::filesystem::u8path(file_path), std::ios::binary);
if (!file.is_open()) {
LOG_ERROR("failed to open '%s'", file_path.c_str());
failed = true;
+20 -1
View File
@@ -111,6 +111,7 @@ const char* model_version_to_str[] = {
"Ming-Image",
"Z-Image L2P",
"Iris-3B",
"Flux.3 Image Klein",
};
static_assert(VERSION_COUNT == sizeof(model_version_to_str) / sizeof(model_version_to_str[0]),
@@ -1514,6 +1515,9 @@ bool StableDiffusionGGML::build_denoiser() {
} else {
default_flow_shift = 3.f;
}
} else if (sd_version_is_flux3(version)) {
pred_type = FLUX_FLOW_PRED;
default_flow_shift = 6.93f;
} else if (sd_version_is_flux(version) ||
sd_version_is_flux2(version) ||
sd_version_is_longcat(version) ||
@@ -1579,7 +1583,11 @@ bool StableDiffusionGGML::build_denoiser() {
}
case FLUX_FLOW_PRED: {
LOG_INFO("running in Flux FLOW mode");
denoiser = std::make_shared<FluxFlowDenoiser>();
if (sd_version_is_flux3(version)) {
denoiser = std::make_shared<Flux3FlowDenoiser>();
} else {
denoiser = std::make_shared<FluxFlowDenoiser>();
}
break;
}
case SEFI_FLOW_PRED: {
@@ -2558,6 +2566,13 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
if (sd_version_is_hunyuan_video(version) && step + 1 < sigmas.size()) {
hunyuan_timestep_r_tensor = sd::Tensor<float>::from_vector({sigmas[step + 1]});
}
sd::Tensor<float> flux3_target_timestep;
if (version == VERSION_FLUX3) {
// The sign marks intermediate evaluations; the magnitude is the one-based step.
const size_t target_index = static_cast<size_t>(std::abs(step));
GGML_ASSERT(target_index > 0 && target_index < sigmas.size());
flux3_target_timestep = sd::Tensor<float>::from_vector({sigmas[target_index]});
}
sd::Tensor<float> noised_input = x * c_in;
if (apply_denoise_mask && (version == VERSION_WAN2_2_TI2V || sd_version_is_ltxav(version) || sd_version_is_lingbot_video(version))) {
noised_input = noised_input * denoise_mask + sampling_init_latent * (1.0f - denoise_mask);
@@ -2634,6 +2649,8 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
} else if (sd_version_is_flux(version) || sd_version_is_flux2(version) || sd_version_is_longcat(version) || sd_version_is_sefi_image(version)) {
diffusion_params.extra = FluxDiffusionExtra{&guidance_tensor,
local_skip_layers};
} else if (sd_version_is_flux3(version)) {
diffusion_params.extra = Flux3DiffusionExtra{flux3_target_timestep.empty() ? nullptr : &flux3_target_timestep};
} else if (version == VERSION_QWEN_IMAGE_2_1) {
diffusion_params.extra = QwenImage21DiffusionExtra{&condition.c_token_types};
} else if (sd_version_is_anima(version)) {
@@ -3180,6 +3197,8 @@ std::string StableDiffusionGGML::get_default_ref_image_preset(SDVersion version)
return "longcat";
} else if (sd_version_is_flux(version)) {
return "flux_kontext";
} else if (sd_version_is_flux3(version)) {
return "flux2";
} else if (sd_version_is_flux2(version) || sd_version_is_sefi_image(version)) {
return "flux2";
} else if (version == VERSION_QWEN_IMAGE_LAYERED) {
+6
View File
@@ -15,6 +15,7 @@
#include "model/diffusion/control.hpp"
#include "model/diffusion/ernie_image.hpp"
#include "model/diffusion/flux.hpp"
#include "model/diffusion/flux3.hpp"
#include "model/diffusion/hidream_o1.hpp"
#include "model/diffusion/hunyuan.hpp"
#include "model/diffusion/ideogram4.hpp"
@@ -173,6 +174,11 @@ namespace sd::model_builders {
version,
weight_manager,
sd_ctx_params->model_args);
} else if (sd_version_is_flux3(version)) {
result.conditioner = std::make_shared<LLMEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
tensor_storage_map, version, "", false, weight_manager, tokenizers);
result.diffusion = std::make_shared<Flux3::Flux3Runner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
tensor_storage_map, "model.diffusion_model", weight_manager);
} else if (sd_version_is_flux2(version) || sd_version_is_sefi_image(version)) {
bool is_chroma = false;
result.conditioner = std::make_shared<LLMEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
+23
View File
@@ -1452,6 +1452,29 @@ struct FluxFlowDenoiser : public DiscreteFlowDenoiser {
}
};
struct Flux3FlowDenoiser : public FluxFlowDenoiser {
float t_to_sigma(float t) override {
float u = (t + 1.f) / TIMESTEPS;
return shift * u / (1.f + (shift - 1.f) * u);
}
std::vector<float> get_sigmas(uint32_t n, int image_seq_len, scheduler_t scheduler_type, SDVersion version, const char* extra_sample_args = nullptr) override {
if (scheduler_type != DISCRETE_SCHEDULER) {
return Denoiser::get_sigmas(n, image_seq_len, scheduler_type, version, extra_sample_args);
}
if (n == 0) {
return {};
}
std::vector<float> sigmas;
sigmas.reserve(n + 1);
for (uint32_t i = 0; i <= n; ++i) {
float u = 1.f - static_cast<float>(i) / n;
sigmas.push_back(shift * u / (1.f + (shift - 1.f) * u));
}
return sigmas;
}
};
struct SefiFlowDenoiser;
struct SefiFlowDenoiser : public FluxFlowDenoiser {
+3 -2
View File
@@ -10,6 +10,7 @@
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <filesystem>
static IMatrixCollector imatrix_collector;
@@ -227,7 +228,7 @@ void IMatrixCollector::save_imatrix(std::string fname, int ncall) const {
LOG_WARN("storing only %zu out of %zu entries\n", to_store.size(), stats_.size());
}
std::ofstream out(fname, std::ios::binary);
std::ofstream out(std::filesystem::u8path(fname), std::ios::binary);
out.write((const char*)&n_entries, sizeof(n_entries));
for (const auto& name : to_store) {
const auto& stat = stats_.at(name);
@@ -251,7 +252,7 @@ void IMatrixCollector::save_imatrix(std::string fname, int ncall) const {
}
bool IMatrixCollector::load_imatrix(const char* fname) {
std::ifstream in(fname, std::ios::binary);
std::ifstream in(std::filesystem::u8path(fname), std::ios::binary);
if (!in) {
LOG_ERROR("failed to open %s\n", fname);
return false;
+2 -1
View File
@@ -4,6 +4,7 @@
#include <array>
#include <climits>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <queue>
#include <sstream>
@@ -495,7 +496,7 @@ struct HFTokenizer::Impl {
HFTokenizer::HFTokenizer(const std::string& path)
: impl_(new Impl) {
std::ifstream stream(path, std::ios::binary);
std::ifstream stream(std::filesystem::u8path(path), std::ios::binary);
tokenizer_require(stream.good(), "cannot open " + path);
TokenizerJSON config;
stream >> config;