Compare commits

...

6 Commits

15 changed files with 231 additions and 123 deletions

View File

@ -24,10 +24,9 @@ You can download the preconverted gguf weights from [silveroxides/Chroma-GGUF](h
For example: For example:
``` ```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\chroma-unlocked-v40-q8_0.gguf --vae ..\models\ae.sft --t5xxl ..\models\t5xxl_fp16.safetensors -p "a lovely cat holding a sign says 'chroma.cpp'" --cfg-scale 4.0 --sampling-method euler -v --chroma-disable-dit-mask --clip-on-cpu .\bin\Release\sd-cli.exe --diffusion-model ..\models\chroma-unlocked-v40-q8_0.gguf --vae ..\models\ae.sft --t5xxl ..\models\t5xxl_fp16.safetensors -p "a lovely cat holding a sign says 'chroma.cpp'" --cfg-scale 4.0 --sampling-method euler -v --model-args chroma_use_dit_mask=false --clip-on-cpu
``` ```
![](../assets/flux/chroma_v40.png) ![](../assets/flux/chroma_v40.png)

View File

@ -53,6 +53,26 @@ Per-module assignments can target only the largest modules:
See [backend selection](./backend.md) for full syntax. See [backend selection](./backend.md) for full syntax.
## Run models that don't fit in VRAM (CPU streaming).
`--offload-to-cpu` alone keeps every parameter in system RAM and stages it to the runtime backend on first use, then leaves it resident there. If the diffusion model is larger than the runtime backend's free memory (e.g. Flux dev at bf16 on an 8 GiB GPU), that residency stops fitting during the sampling loop and generation fails. Two additional flags make it fit by trading a small amount of speed for room:
- `--max-vram <GiB>` sets a VRAM budget the graph-cut segmenter respects. It cuts each forward pass into segments sized to fit the budget, running them in sequence and freeing intermediate activations between them. Negative values auto-detect free VRAM and spare the given amount (`--max-vram -1` uses most of the free VRAM and keeps ~1 GiB headroom), a positive value caps the budget, `0` disables segmentation.
- `--stream-layers` streams the diffusion model's transformer blocks one at a time. Each block's parameters are copied from the CPU to the runtime backend just before it runs and evicted when the residency budget is reached. Prefetching hides most of the copy latency behind compute. This flag only takes effect when the diffusion params backend is CPU, so it must be combined with `--offload-to-cpu` (or an explicit `--params-backend diffusion=cpu`); a warning is logged and the flag is ignored otherwise.
The three flags stack. The recommended shape for "biggest model my card can host":
```shell
sd-cli --diffusion-model flux1-dev.safetensors ... \
--offload-to-cpu --max-vram -1 --stream-layers
```
- `--offload-to-cpu`: params in RAM, staged as needed.
- `--max-vram -1`: use most of the free VRAM as the compute budget, spare 1 GiB headroom, let the graph-cut segmenter split each forward pass to fit.
- `--stream-layers`: on top of the segmenter, stream individual transformer blocks so their weights don't all need to be resident at once.
Ordered from fastest to smallest-VRAM: no flags → `--offload-to-cpu``--offload-to-cpu --max-vram <N>``--offload-to-cpu --max-vram <N> --stream-layers`. Each step down costs a few percent of throughput to buy more room; combined they can run models roughly 3-4x larger than the raw VRAM would allow.
## Use quantization to reduce memory usage. ## Use quantization to reduce memory usage.
[quantization](./quantization_and_gguf.md) [quantization](./quantization_and_gguf.md)

View File

@ -39,10 +39,10 @@
### Qwen Image Edit 2511 ### Qwen Image Edit 2511
To use the new Qwen Image Edit 2511 mode, the `--qwen-image-zero-cond-t` flag must be enabled; otherwise, image editing quality will degrade significantly. To use the new Qwen Image Edit 2511 mode, `--model-args qwen_image_zero_cond_t=true` must be enabled; otherwise, image editing quality will degrade significantly.
``` ```
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --qwen-image-zero-cond-t .\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\qwen-image-edit-2511-Q4_K_M.gguf --vae ..\models\vae\qwen_image_vae.safetensors --llm ..\models\text_encoders\qwen_2.5_vl_7b.safetensors --cfg-scale 2.5 --sampling-method euler -v --offload-to-cpu --diffusion-fa --flow-shift 3 -r ..\assets\flux\flux1-dev-q8_0.png -p "change 'flux.cpp' to 'edit.cpp'" --model-args qwen_image_zero_cond_t=true
``` ```
<img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2511.png" /> <img alt="qwen_image_edit_2509" src="../assets/qwen/qwen_image_edit_2511.png" />

View File

@ -443,6 +443,12 @@ ArgOptions SDContextParams::get_options() {
"weight type per tensor pattern (example: \"^vae\\.=f16,model\\.=q8_0\")", "weight type per tensor pattern (example: \"^vae\\.=f16,model\\.=q8_0\")",
(int)',', (int)',',
&tensor_type_rules}, &tensor_type_rules},
{"",
"--model-args",
"extra model args, key=value list. Supports chroma_use_dit_mask, chroma_use_t5_mask, "
"chroma_t5_mask_pad, qwen_image_zero_cond_t",
(int)',',
&model_args},
{"", {"",
"--photo-maker", "--photo-maker",
"path to PHOTOMAKER model", "path to PHOTOMAKER model",
@ -493,10 +499,6 @@ ArgOptions SDContextParams::get_options() {
"number of threads to use during computation (default: -1). " "number of threads to use during computation (default: -1). "
"If threads <= 0, then threads will be set to the number of CPU physical cores", "If threads <= 0, then threads will be set to the number of CPU physical cores",
&n_threads}, &n_threads},
{"",
"--chroma-t5-mask-pad",
"t5 mask pad size of chroma",
&chroma_t5_mask_pad},
}; };
options.bool_options = { options.bool_options = {
@ -554,30 +556,6 @@ ArgOptions SDContextParams::get_options() {
"--vae-conv-direct", "--vae-conv-direct",
"use ggml_conv2d_direct in the vae model", "use ggml_conv2d_direct in the vae model",
true, &vae_conv_direct}, true, &vae_conv_direct},
{"",
"--circular",
"enable circular padding for convolutions",
true, &circular},
{"",
"--circularx",
"enable circular RoPE wrapping on x-axis (width) only",
true, &circular_x},
{"",
"--circulary",
"enable circular RoPE wrapping on y-axis (height) only",
true, &circular_y},
{"",
"--chroma-disable-dit-mask",
"disable dit mask for chroma",
false, &chroma_use_dit_mask},
{"",
"--qwen-image-zero-cond-t",
"enable zero_cond_t for qwen image",
true, &qwen_image_zero_cond_t},
{"",
"--chroma-enable-t5-mask",
"enable t5 mask for chroma",
true, &chroma_use_t5_mask},
}; };
auto on_type_arg = [&](int argc, const char** argv, int index) { auto on_type_arg = [&](int argc, const char** argv, int index) {
@ -844,6 +822,7 @@ std::string SDContextParams::to_string() const {
<< " backend: \"" << backend << "\",\n" << " backend: \"" << backend << "\",\n"
<< " params_backend: \"" << params_backend << "\",\n" << " params_backend: \"" << params_backend << "\",\n"
<< " split_mode: \"" << split_mode << "\",\n" << " split_mode: \"" << split_mode << "\",\n"
<< " model_args: \"" << model_args << "\",\n"
<< " auto_fit: " << (auto_fit ? "true" : "false") << ",\n" << " auto_fit: " << (auto_fit ? "true" : "false") << ",\n"
<< " enable_mmap: " << (enable_mmap ? "true" : "false") << ",\n" << " enable_mmap: " << (enable_mmap ? "true" : "false") << ",\n"
<< " control_net_cpu: " << (control_net_cpu ? "true" : "false") << ",\n" << " control_net_cpu: " << (control_net_cpu ? "true" : "false") << ",\n"
@ -853,13 +832,6 @@ std::string SDContextParams::to_string() const {
<< " diffusion_flash_attn: " << (diffusion_flash_attn ? "true" : "false") << ",\n" << " diffusion_flash_attn: " << (diffusion_flash_attn ? "true" : "false") << ",\n"
<< " diffusion_conv_direct: " << (diffusion_conv_direct ? "true" : "false") << ",\n" << " diffusion_conv_direct: " << (diffusion_conv_direct ? "true" : "false") << ",\n"
<< " vae_conv_direct: " << (vae_conv_direct ? "true" : "false") << ",\n" << " vae_conv_direct: " << (vae_conv_direct ? "true" : "false") << ",\n"
<< " circular: " << (circular ? "true" : "false") << ",\n"
<< " circular_x: " << (circular_x ? "true" : "false") << ",\n"
<< " circular_y: " << (circular_y ? "true" : "false") << ",\n"
<< " chroma_use_dit_mask: " << (chroma_use_dit_mask ? "true" : "false") << ",\n"
<< " qwen_image_zero_cond_t: " << (qwen_image_zero_cond_t ? "true" : "false") << ",\n"
<< " chroma_use_t5_mask: " << (chroma_use_t5_mask ? "true" : "false") << ",\n"
<< " chroma_t5_mask_pad: " << chroma_t5_mask_pad << ",\n"
<< " prediction: " << sd_prediction_name(prediction) << ",\n" << " prediction: " << sd_prediction_name(prediction) << ",\n"
<< " lora_apply_mode: " << sd_lora_apply_mode_name(lora_apply_mode) << ",\n" << " lora_apply_mode: " << sd_lora_apply_mode_name(lora_apply_mode) << ",\n"
<< " force_sdxl_vae_conv_scale: " << (force_sdxl_vae_conv_scale ? "true" : "false") << "\n" << " force_sdxl_vae_conv_scale: " << (force_sdxl_vae_conv_scale ? "true" : "false") << "\n"
@ -912,13 +884,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
sd_ctx_params.tae_preview_only = taesd_preview; sd_ctx_params.tae_preview_only = taesd_preview;
sd_ctx_params.diffusion_conv_direct = diffusion_conv_direct; sd_ctx_params.diffusion_conv_direct = diffusion_conv_direct;
sd_ctx_params.vae_conv_direct = vae_conv_direct; sd_ctx_params.vae_conv_direct = vae_conv_direct;
sd_ctx_params.circular_x = circular || circular_x;
sd_ctx_params.circular_y = circular || circular_y;
sd_ctx_params.force_sdxl_vae_conv_scale = force_sdxl_vae_conv_scale; sd_ctx_params.force_sdxl_vae_conv_scale = force_sdxl_vae_conv_scale;
sd_ctx_params.chroma_use_dit_mask = chroma_use_dit_mask;
sd_ctx_params.chroma_use_t5_mask = chroma_use_t5_mask;
sd_ctx_params.chroma_t5_mask_pad = chroma_t5_mask_pad;
sd_ctx_params.qwen_image_zero_cond_t = qwen_image_zero_cond_t;
sd_ctx_params.vae_format = str_to_vae_format(vae_format); sd_ctx_params.vae_format = str_to_vae_format(vae_format);
sd_ctx_params.max_vram = max_vram.c_str(); sd_ctx_params.max_vram = max_vram.c_str();
sd_ctx_params.stream_layers = stream_layers; sd_ctx_params.stream_layers = stream_layers;
@ -928,6 +894,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
sd_ctx_params.split_mode = split_mode.c_str(); sd_ctx_params.split_mode = split_mode.c_str();
sd_ctx_params.auto_fit = auto_fit; sd_ctx_params.auto_fit = auto_fit;
sd_ctx_params.rpc_servers = rpc_servers.c_str(); sd_ctx_params.rpc_servers = rpc_servers.c_str();
sd_ctx_params.model_args = model_args.empty() ? nullptr : model_args.c_str();
return sd_ctx_params; return sd_ctx_params;
} }
@ -1189,6 +1156,18 @@ ArgOptions SDGenerationParams::get_options() {
"disable auto resize of ref images", "disable auto resize of ref images",
false, false,
&auto_resize_ref_image}, &auto_resize_ref_image},
{"",
"--circular",
"enable circular padding on both axes for tileable output",
true, &circular},
{"",
"--circularx",
"enable circular padding on x-axis (width) only",
true, &circular_x},
{"",
"--circulary",
"enable circular padding on y-axis (height) only",
true, &circular_y},
{"", {"",
"--disable-image-metadata", "--disable-image-metadata",
"do not embed generation metadata on image files", "do not embed generation metadata on image files",
@ -2475,6 +2454,8 @@ sd_img_gen_params_t SDGenerationParams::to_sd_img_gen_params_t() {
params.hires.upscale_tile_size = hires_upscale_tile_size; params.hires.upscale_tile_size = hires_upscale_tile_size;
params.hires.custom_sigmas = hires_custom_sigmas.empty() ? nullptr : hires_custom_sigmas.data(); params.hires.custom_sigmas = hires_custom_sigmas.empty() ? nullptr : hires_custom_sigmas.data();
params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size()); params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size());
params.circular_x = circular || circular_x;
params.circular_y = circular || circular_y;
return params; return params;
} }
@ -2540,6 +2521,8 @@ sd_vid_gen_params_t SDGenerationParams::to_sd_vid_gen_params_t() {
params.hires.upscale_tile_size = hires_upscale_tile_size; params.hires.upscale_tile_size = hires_upscale_tile_size;
params.hires.custom_sigmas = hires_custom_sigmas.empty() ? nullptr : hires_custom_sigmas.data(); params.hires.custom_sigmas = hires_custom_sigmas.empty() ? nullptr : hires_custom_sigmas.data();
params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size()); params.hires.custom_sigmas_count = static_cast<int>(hires_custom_sigmas.size());
params.circular_x = circular || circular_x;
params.circular_y = circular || circular_y;
return params; return params;
} }

View File

@ -152,6 +152,7 @@ struct SDContextParams {
std::string backend; std::string backend;
std::string params_backend; std::string params_backend;
std::string split_mode; std::string split_mode;
std::string model_args;
bool auto_fit = false; bool auto_fit = false;
std::string rpc_servers; std::string rpc_servers;
std::string effective_backend; std::string effective_backend;
@ -165,16 +166,6 @@ struct SDContextParams {
bool diffusion_conv_direct = false; bool diffusion_conv_direct = false;
bool vae_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; prediction_t prediction = PREDICTION_COUNT;
lora_apply_mode_t lora_apply_mode = LORA_APPLY_AUTO; lora_apply_mode_t lora_apply_mode = LORA_APPLY_AUTO;
@ -246,6 +237,10 @@ struct SDGenerationParams {
int upscale_repeats = 1; int upscale_repeats = 1;
int upscale_tile_size = 128; int upscale_tile_size = 128;
bool circular = false;
bool circular_x = false;
bool circular_y = false;
bool hires_enabled = false; bool hires_enabled = false;
std::string hires_upscaler = "Latent"; std::string hires_upscaler = "Latent";
std::string hires_upscaler_model_path; std::string hires_upscaler_model_path;

View File

@ -216,13 +216,7 @@ typedef struct {
bool tae_preview_only; bool tae_preview_only;
bool diffusion_conv_direct; bool diffusion_conv_direct;
bool vae_conv_direct; bool vae_conv_direct;
bool circular_x;
bool circular_y;
bool force_sdxl_vae_conv_scale; bool force_sdxl_vae_conv_scale;
bool chroma_use_dit_mask;
bool chroma_use_t5_mask;
int chroma_t5_mask_pad;
bool qwen_image_zero_cond_t;
enum sd_vae_format_t vae_format; enum sd_vae_format_t vae_format;
const char* max_vram; // GiB budget or backend assignment spec for graph-cut segmented param offload (0 = disabled, -1 = auto) const char* max_vram; // GiB budget or backend assignment spec for graph-cut segmented param offload (0 = disabled, -1 = auto)
bool stream_layers; // Enable residency+prefetch streaming on top of --max-vram (no effect without --max-vram) bool stream_layers; // Enable residency+prefetch streaming on top of --max-vram (no effect without --max-vram)
@ -232,6 +226,7 @@ typedef struct {
const char* split_mode; // weight distribution for multi-device modules: layer (default) or row, or per-module assignments e.g. "diffusion=row" const char* split_mode; // weight distribution for multi-device modules: layer (default) or row, or per-module assignments e.g. "diffusion=row"
bool auto_fit; bool auto_fit;
const char* rpc_servers; const char* rpc_servers;
const char* model_args;
} sd_ctx_params_t; } sd_ctx_params_t;
typedef struct { typedef struct {
@ -385,6 +380,8 @@ typedef struct {
sd_cache_params_t cache; sd_cache_params_t cache;
sd_hires_params_t hires; sd_hires_params_t hires;
int qwen_image_layers; int qwen_image_layers;
bool circular_x;
bool circular_y;
} sd_img_gen_params_t; } sd_img_gen_params_t;
typedef struct { typedef struct {
@ -410,6 +407,8 @@ typedef struct {
sd_tiling_params_t vae_tiling_params; sd_tiling_params_t vae_tiling_params;
sd_cache_params_t cache; sd_cache_params_t cache;
sd_hires_params_t hires; sd_hires_params_t hires;
bool circular_x;
bool circular_y;
} sd_vid_gen_params_t; } sd_vid_gen_params_t;
typedef struct sd_ctx_t sd_ctx_t; typedef struct sd_ctx_t sd_ctx_t;

View File

@ -6,6 +6,7 @@
#include <optional> #include <optional>
#include "core/tensor_ggml.hpp" #include "core/tensor_ggml.hpp"
#include "core/util.h"
#include "model/te/clip.hpp" #include "model/te/clip.hpp"
#include "model/te/llm.hpp" #include "model/te/llm.hpp"
#include "model/te/t5.hpp" #include "model/te/t5.hpp"
@ -1217,8 +1218,27 @@ struct T5CLIPEmbedder : public Conditioner {
bool use_mask = false, bool use_mask = false,
int mask_pad = 0, int mask_pad = 0,
bool is_umt5 = false, bool is_umt5 = false,
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr) std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
const char* model_args = nullptr)
: use_mask(use_mask), mask_pad(mask_pad), t5_tokenizer(is_umt5) { : use_mask(use_mask), mask_pad(mask_pad), t5_tokenizer(is_umt5) {
for (const auto& [key, value] : parse_key_value_args(model_args, "model arg")) {
if (key == "chroma_use_t5_mask") {
bool parsed = false;
if (parse_strict_bool(value, parsed)) {
this->use_mask = parsed;
} else {
LOG_WARN("ignoring invalid Chroma T5 model arg '%s=%s'", key.c_str(), value.c_str());
}
} else if (key == "chroma_t5_mask_pad") {
int parsed = 0;
if (parse_strict_int(value, parsed)) {
this->mask_pad = parsed;
} else {
LOG_WARN("ignoring invalid Chroma T5 model arg '%s=%s'", key.c_str(), value.c_str());
}
}
}
bool use_t5 = false; bool use_t5 = false;
for (auto pair : tensor_storage_map) { for (auto pair : tensor_storage_map) {
if (pair.first.find("text_encoders.t5xxl") != std::string::npos) { if (pair.first.find("text_encoders.t5xxl") != std::string::npos) {

View File

@ -5,7 +5,8 @@
#include "model_loader.h" #include "model_loader.h"
#include "model_manager.h" #include "model_manager.h"
#define CONTROL_NET_GRAPH_SIZE 1536 // Match main UNet's MAX_GRAPH_SIZE so SDXL ControlNet (transformer_depth={1,2,10}) fits.
#define CONTROL_NET_GRAPH_SIZE MAX_GRAPH_SIZE
/* /*
=================================== ControlNet =================================== =================================== ControlNet ===================================

View File

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "core/util.h"
#include "model/adapter/pulid.hpp" #include "model/adapter/pulid.hpp"
#include "model/common/rope.hpp" #include "model/common/rope.hpp"
#include "model/diffusion/dit.hpp" #include "model/diffusion/dit.hpp"
@ -1400,18 +1401,28 @@ namespace Flux {
std::vector<float> dct_vec; std::vector<float> dct_vec;
sd::Tensor<float> guidance_tensor; sd::Tensor<float> guidance_tensor;
SDVersion version; SDVersion version;
bool use_mask = false; bool use_mask = true;
FluxRunner(ggml_backend_t backend, FluxRunner(ggml_backend_t backend,
const String2TensorStorage& tensor_storage_map = {}, const String2TensorStorage& tensor_storage_map = {},
const std::string prefix = "", const std::string prefix = "",
SDVersion version = VERSION_FLUX, SDVersion version = VERSION_FLUX,
bool use_mask = false, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr) const char* model_args = nullptr)
: DiffusionModelRunner(backend, prefix, weight_manager), : DiffusionModelRunner(backend, prefix, weight_manager),
config(FluxConfig::detect_from_weights(tensor_storage_map, prefix, version)), config(FluxConfig::detect_from_weights(tensor_storage_map, prefix, version)),
version(version), version(version) {
use_mask(use_mask) { for (const auto& [key, value] : parse_key_value_args(model_args, "model arg")) {
if (key == "chroma_use_dit_mask") {
bool parsed = true;
if (parse_strict_bool(value, parsed)) {
use_mask = parsed;
} else {
LOG_WARN("ignoring invalid Chroma DiT model arg '%s=%s'", key.c_str(), value.c_str());
}
}
}
if (config.is_chroma) { if (config.is_chroma) {
LOG_INFO("Using pruned modulation (Chroma)"); LOG_INFO("Using pruned modulation (Chroma)");
} }
@ -1718,7 +1729,6 @@ namespace Flux {
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
VERSION_FLUX2, VERSION_FLUX2,
false,
model_manager); model_manager);
if (!model_manager->register_runner_params("Flux test", if (!model_manager->register_runner_params("Flux test",

View File

@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include "core/util.h"
#include "model/common/block.hpp" #include "model/common/block.hpp"
#include "model/diffusion/dit.hpp" #include "model/diffusion/dit.hpp"
#include "model/diffusion/flux.hpp" #include "model/diffusion/flux.hpp"
@ -566,12 +567,21 @@ namespace Qwen {
const String2TensorStorage& tensor_storage_map = {}, const String2TensorStorage& tensor_storage_map = {},
const std::string prefix = "", const std::string prefix = "",
SDVersion version = VERSION_QWEN_IMAGE, SDVersion version = VERSION_QWEN_IMAGE,
bool zero_cond_t = false, std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr) const char* model_args = nullptr)
: DiffusionModelRunner(backend, prefix, weight_manager), : DiffusionModelRunner(backend, prefix, weight_manager),
config(QwenImageConfig::detect_from_weights(tensor_storage_map, prefix)), config(QwenImageConfig::detect_from_weights(tensor_storage_map, prefix)),
version(version) { version(version) {
config.zero_cond_t = config.zero_cond_t || zero_cond_t; for (const auto& [key, value] : parse_key_value_args(model_args, "model arg")) {
if (key == "qwen_image_zero_cond_t") {
bool parsed = false;
if (parse_strict_bool(value, parsed)) {
config.zero_cond_t = config.zero_cond_t || parsed;
} else {
LOG_WARN("ignoring invalid Qwen Image model arg '%s=%s'", key.c_str(), value.c_str());
}
}
}
if (version == VERSION_QWEN_IMAGE_LAYERED) { if (version == VERSION_QWEN_IMAGE_LAYERED) {
config.use_additional_t_cond = true; config.use_additional_t_cond = true;
} }
@ -775,7 +785,6 @@ namespace Qwen {
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
VERSION_QWEN_IMAGE, VERSION_QWEN_IMAGE,
false,
model_manager); model_manager);
if (!model_manager->register_runner_params("Qwen image test", if (!model_manager->register_runner_params("Qwen image test",

View File

@ -43,7 +43,7 @@ bool is_safetensors_file(const std::string& file_path) {
} }
size_t header_size_ = model_io::read_u64(header_size_buf); size_t header_size_ = model_io::read_u64(header_size_buf);
if (header_size_ >= file_size_ || header_size_ <= 2) { if (header_size_ > file_size_ - ST_HEADER_SIZE_LEN || header_size_ <= 2) {
return false; return false;
} }
@ -114,10 +114,11 @@ bool read_safetensors_file(const std::string& file_path,
} }
size_t header_size_ = model_io::read_u64(header_size_buf); size_t header_size_ = model_io::read_u64(header_size_buf);
if (header_size_ >= file_size_) { if (header_size_ > file_size_ - ST_HEADER_SIZE_LEN) {
set_error(error, "invalid safetensor file '" + file_path + "'"); set_error(error, "invalid safetensor file '" + file_path + "'");
return false; return false;
} }
const size_t data_start = ST_HEADER_SIZE_LEN + header_size_;
// read header // read header
std::vector<char> header_buf; std::vector<char> header_buf;
@ -156,6 +157,10 @@ bool read_safetensors_file(const std::string& file_path,
size_t begin = tensor_info["data_offsets"][0].get<size_t>(); size_t begin = tensor_info["data_offsets"][0].get<size_t>();
size_t end = tensor_info["data_offsets"][1].get<size_t>(); size_t end = tensor_info["data_offsets"][1].get<size_t>();
if (begin > end || end > file_size_ - data_start) {
set_error(error, "data offsets out of bounds for tensor '" + name + "'");
return false;
}
ggml_type type = safetensors_dtype_to_ggml_type(dtype); ggml_type type = safetensors_dtype_to_ggml_type(dtype);
if (type == GGML_TYPE_COUNT) { if (type == GGML_TYPE_COUNT) {
@ -187,7 +192,7 @@ bool read_safetensors_file(const std::string& file_path,
n_dims = 1; n_dims = 1;
} }
TensorStorage tensor_storage(name, type, ne, n_dims, 0, ST_HEADER_SIZE_LEN + header_size_ + begin); TensorStorage tensor_storage(name, type, ne, n_dims, 0, data_start + begin);
tensor_storage.reverse_ne(); tensor_storage.reverse_ne();
size_t tensor_data_size = end - begin; size_t tensor_data_size = end - begin;

View File

@ -736,6 +736,62 @@ std::string convert_diffusers_dit_to_original_krea2(std::string name) {
return name; return name;
} }
// Convert a diffusers-format ControlNet tensor name to the original (LDM/lllyasviel) layout
// declared by ControlNetBlock. Reuses the UNet down/mid conversion for the shared encoder
// (down_blocks, mid_block, time_embedding, add_embedding, conv_in) and adds the ControlNet-only
// mappings: input_hint_block, zero_convs, middle_block_out.
std::string convert_diffusers_controlnet_to_original_sdxl(std::string name) {
name = convert_diffusers_unet_to_original_sdxl(std::move(name));
static const std::vector<std::pair<std::string, std::string>> prefix_map = {
{"controlnet_cond_embedding.conv_in.", "input_hint_block.0."},
{"controlnet_cond_embedding.blocks.0.", "input_hint_block.2."},
{"controlnet_cond_embedding.blocks.1.", "input_hint_block.4."},
{"controlnet_cond_embedding.blocks.2.", "input_hint_block.6."},
{"controlnet_cond_embedding.blocks.3.", "input_hint_block.8."},
{"controlnet_cond_embedding.blocks.4.", "input_hint_block.10."},
{"controlnet_cond_embedding.blocks.5.", "input_hint_block.12."},
{"controlnet_cond_embedding.conv_out.", "input_hint_block.14."},
{"controlnet_mid_block.", "middle_block_out.0."},
};
for (const auto& p : prefix_map) {
if (starts_with(name, p.first)) {
return p.second + name.substr(p.first.size());
}
}
static const std::string controlnet_down_prefix = "controlnet_down_blocks.";
if (starts_with(name, controlnet_down_prefix)) {
size_t rest_start = controlnet_down_prefix.size();
size_t dot = name.find('.', rest_start);
if (dot != std::string::npos) {
std::string idx = name.substr(rest_start, dot - rest_start);
return "zero_convs." + idx + ".0" + name.substr(dot);
}
}
return name;
}
static bool is_diffusers_controlnet_name(const std::string& name) {
static const std::vector<std::string> heads = {
"controlnet_cond_embedding.",
"controlnet_down_blocks.",
"controlnet_mid_block.",
"down_blocks.",
"mid_block.",
"time_embedding.",
"add_embedding.",
"conv_in.",
};
for (const auto& h : heads) {
if (starts_with(name, h)) {
return true;
}
}
return false;
}
std::string convert_diffusion_model_name(std::string name, std::string prefix, SDVersion version) { std::string convert_diffusion_model_name(std::string name, std::string prefix, SDVersion version) {
if (sd_version_is_sd1(version) || sd_version_is_sd2(version)) { if (sd_version_is_sd1(version) || sd_version_is_sd2(version)) {
name = convert_diffusers_unet_to_original_sd1(name); name = convert_diffusers_unet_to_original_sd1(name);
@ -1338,6 +1394,9 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
name = name.substr(pos + 1); name = name.substr(pos + 1);
} }
} }
if (sd_version_is_sdxl(version) && is_diffusers_controlnet_name(name)) {
name = convert_diffusers_controlnet_to_original_sdxl(name);
}
} }
if (is_lora) { if (is_lora) {

View File

@ -287,6 +287,10 @@ bool IMatrixCollector::load_imatrix(const char* fname) {
if (e.values.empty()) { if (e.values.empty()) {
e.values.resize(nval, 0); e.values.resize(nval, 0);
e.counts.resize(nval, 0); e.counts.resize(nval, 0);
} else if (e.values.size() != (size_t)nval) {
LOG_ERROR("inconsistent size for a repeated entry (%d vs %d)\n", (int)e.values.size(), nval);
stats_ = {};
return false;
} }
std::vector<float> tmp(nval); std::vector<float> tmp(nval);

View File

@ -57,11 +57,11 @@
#include "name_conversion.h" #include "name_conversion.h"
#include "runtime/latent-preview.h" #include "runtime/latent-preview.h"
#include <atomic>
const char* sd_vae_format_name(enum sd_vae_format_t format); const char* sd_vae_format_name(enum sd_vae_format_t format);
static SDVersion sd_vae_format_to_version(enum sd_vae_format_t format, SDVersion fallback); static SDVersion sd_vae_format_to_version(enum sd_vae_format_t format, SDVersion fallback);
#include <atomic>
const char* model_version_to_str[] = { const char* model_version_to_str[] = {
"SD 1.x", "SD 1.x",
"SD 1.x Inpaint", "SD 1.x Inpaint",
@ -863,10 +863,6 @@ public:
use_tae = true; use_tae = true;
} }
if (sd_ctx_params->circular_x || sd_ctx_params->circular_y) {
LOG_INFO("Using circular padding for convolutions");
}
{ {
if (!ensure_backend_pair(SDBackendModule::TE) || if (!ensure_backend_pair(SDBackendModule::TE) ||
!ensure_backend_pair(SDBackendModule::DIFFUSION)) { !ensure_backend_pair(SDBackendModule::DIFFUSION)) {
@ -925,10 +921,11 @@ public:
if (is_chroma) { if (is_chroma) {
cond_stage_model = std::make_shared<T5CLIPEmbedder>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<T5CLIPEmbedder>(backend_for(SDBackendModule::TE),
tensor_storage_map, tensor_storage_map,
sd_ctx_params->chroma_use_t5_mask,
sd_ctx_params->chroma_t5_mask_pad,
false, false,
model_manager); 1,
false,
model_manager,
sd_ctx_params->model_args);
} else if (version == VERSION_OVIS_IMAGE) { } else if (version == VERSION_OVIS_IMAGE) {
cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE),
tensor_storage_map, tensor_storage_map,
@ -945,8 +942,8 @@ public:
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
version, version,
sd_ctx_params->chroma_use_dit_mask, model_manager,
model_manager); sd_ctx_params->model_args);
} else if (sd_version_is_flux2(version) || sd_version_is_sefi_image(version)) { } else if (sd_version_is_flux2(version) || sd_version_is_sefi_image(version)) {
bool is_chroma = false; bool is_chroma = false;
cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE),
@ -959,8 +956,8 @@ public:
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
version, version,
sd_ctx_params->chroma_use_dit_mask, model_manager,
model_manager); sd_ctx_params->model_args);
} else if (sd_version_is_ltxav(version)) { } else if (sd_version_is_ltxav(version)) {
cond_stage_model = std::make_shared<LTXAVEmbedder>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<LTXAVEmbedder>(backend_for(SDBackendModule::TE),
tensor_storage_map, tensor_storage_map,
@ -1018,8 +1015,8 @@ public:
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
version, version,
sd_ctx_params->qwen_image_zero_cond_t, model_manager,
model_manager); sd_ctx_params->model_args);
} else if (sd_version_is_longcat(version)) { } else if (sd_version_is_longcat(version)) {
cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<LLMEmbedder>(backend_for(SDBackendModule::TE),
tensor_storage_map, tensor_storage_map,
@ -1031,8 +1028,8 @@ public:
tensor_storage_map, tensor_storage_map,
"model.diffusion_model", "model.diffusion_model",
version, version,
sd_ctx_params->chroma_use_dit_mask, model_manager,
model_manager); sd_ctx_params->model_args);
} else if (version == VERSION_HIDREAM_O1) { } else if (version == VERSION_HIDREAM_O1) {
cond_stage_model = std::make_shared<HiDreamO1::HiDreamO1Conditioner>(backend_for(SDBackendModule::TE), cond_stage_model = std::make_shared<HiDreamO1::HiDreamO1Conditioner>(backend_for(SDBackendModule::TE),
tensor_storage_map, tensor_storage_map,
@ -1368,16 +1365,6 @@ public:
high_noise_diffusion_model->set_flash_attention_enabled(true); high_noise_diffusion_model->set_flash_attention_enabled(true);
} }
} }
diffusion_model->set_circular_axes(sd_ctx_params->circular_x, sd_ctx_params->circular_y);
if (high_noise_diffusion_model) {
high_noise_diffusion_model->set_circular_axes(sd_ctx_params->circular_x, sd_ctx_params->circular_y);
}
if (control_net) {
control_net->set_circular_axes(sd_ctx_params->circular_x, sd_ctx_params->circular_y);
}
circular_x = sd_ctx_params->circular_x;
circular_y = sd_ctx_params->circular_y;
} }
LOG_DEBUG("validating model metadata"); LOG_DEBUG("validating model metadata");
@ -3061,17 +3048,13 @@ void sd_ctx_params_init(sd_ctx_params_t* sd_ctx_params) {
sd_ctx_params->eager_load = false; sd_ctx_params->eager_load = false;
sd_ctx_params->enable_mmap = false; sd_ctx_params->enable_mmap = false;
sd_ctx_params->diffusion_flash_attn = false; sd_ctx_params->diffusion_flash_attn = false;
sd_ctx_params->circular_x = false;
sd_ctx_params->circular_y = false;
sd_ctx_params->chroma_use_dit_mask = true;
sd_ctx_params->chroma_use_t5_mask = false;
sd_ctx_params->chroma_t5_mask_pad = 1;
sd_ctx_params->vae_format = SD_VAE_FORMAT_AUTO; sd_ctx_params->vae_format = SD_VAE_FORMAT_AUTO;
sd_ctx_params->backend = nullptr; sd_ctx_params->backend = nullptr;
sd_ctx_params->params_backend = nullptr; sd_ctx_params->params_backend = nullptr;
sd_ctx_params->split_mode = nullptr; sd_ctx_params->split_mode = nullptr;
sd_ctx_params->auto_fit = false; sd_ctx_params->auto_fit = false;
sd_ctx_params->rpc_servers = nullptr; sd_ctx_params->rpc_servers = nullptr;
sd_ctx_params->model_args = nullptr;
sd_ctx_params->pulid_weights_path = nullptr; sd_ctx_params->pulid_weights_path = nullptr;
} }
@ -3111,14 +3094,10 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
"backend: %s\n" "backend: %s\n"
"params_backend: %s\n" "params_backend: %s\n"
"split_mode: %s\n" "split_mode: %s\n"
"model_args: %s\n"
"auto_fit: %s\n" "auto_fit: %s\n"
"flash_attn: %s\n" "flash_attn: %s\n"
"diffusion_flash_attn: %s\n" "diffusion_flash_attn: %s\n"
"circular_x: %s\n"
"circular_y: %s\n"
"chroma_use_dit_mask: %s\n"
"chroma_use_t5_mask: %s\n"
"chroma_t5_mask_pad: %d\n"
"vae_format: %s\n", "vae_format: %s\n",
SAFE_STR(sd_ctx_params->model_path), SAFE_STR(sd_ctx_params->model_path),
SAFE_STR(sd_ctx_params->clip_l_path), SAFE_STR(sd_ctx_params->clip_l_path),
@ -3149,14 +3128,10 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
SAFE_STR(sd_ctx_params->backend), SAFE_STR(sd_ctx_params->backend),
SAFE_STR(sd_ctx_params->params_backend), SAFE_STR(sd_ctx_params->params_backend),
SAFE_STR(sd_ctx_params->split_mode), SAFE_STR(sd_ctx_params->split_mode),
SAFE_STR(sd_ctx_params->model_args),
BOOL_STR(sd_ctx_params->auto_fit), BOOL_STR(sd_ctx_params->auto_fit),
BOOL_STR(sd_ctx_params->flash_attn), BOOL_STR(sd_ctx_params->flash_attn),
BOOL_STR(sd_ctx_params->diffusion_flash_attn), BOOL_STR(sd_ctx_params->diffusion_flash_attn),
BOOL_STR(sd_ctx_params->circular_x),
BOOL_STR(sd_ctx_params->circular_y),
BOOL_STR(sd_ctx_params->chroma_use_dit_mask),
BOOL_STR(sd_ctx_params->chroma_use_t5_mask),
sd_ctx_params->chroma_t5_mask_pad,
sd_vae_format_name(sd_ctx_params->vae_format)); sd_vae_format_name(sd_ctx_params->vae_format));
return buf; return buf;
@ -3234,6 +3209,8 @@ void sd_img_gen_params_init(sd_img_gen_params_t* sd_img_gen_params) {
sd_img_gen_params->batch_count = 1; sd_img_gen_params->batch_count = 1;
sd_img_gen_params->control_strength = 0.9f; sd_img_gen_params->control_strength = 0.9f;
sd_img_gen_params->qwen_image_layers = 3; sd_img_gen_params->qwen_image_layers = 3;
sd_img_gen_params->circular_x = false;
sd_img_gen_params->circular_y = false;
sd_img_gen_params->pm_params = {nullptr, 0, nullptr, 20.f}; sd_img_gen_params->pm_params = {nullptr, 0, nullptr, 20.f};
sd_img_gen_params->pulid_params = {nullptr, 1.0f}; sd_img_gen_params->pulid_params = {nullptr, 1.0f};
sd_img_gen_params->vae_tiling_params = {false, false, 0, 0, 0.5f, 0.0f, 0.0f, nullptr}; sd_img_gen_params->vae_tiling_params = {false, false, 0, 0, 0.5f, 0.0f, 0.0f, nullptr};
@ -3267,6 +3244,8 @@ char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params) {
"control_strength: %.2f\n" "control_strength: %.2f\n"
"photo maker: {style_strength = %.2f, id_images_count = %d, id_embed_path = %s}\n" "photo maker: {style_strength = %.2f, id_images_count = %d, id_embed_path = %s}\n"
"VAE tiling: %s (temporal=%s, extra_tiling_args=%s)\n" "VAE tiling: %s (temporal=%s, extra_tiling_args=%s)\n"
"circular_x: %s\n"
"circular_y: %s\n"
"hires: {enabled=%s, upscaler=%s, model_path=%s, scale=%.2f, target=%dx%d, steps=%d, denoising_strength=%.2f}\n", "hires: {enabled=%s, upscaler=%s, model_path=%s, scale=%.2f, target=%dx%d, steps=%d, denoising_strength=%.2f}\n",
SAFE_STR(sd_img_gen_params->prompt), SAFE_STR(sd_img_gen_params->prompt),
SAFE_STR(sd_img_gen_params->negative_prompt), SAFE_STR(sd_img_gen_params->negative_prompt),
@ -3288,6 +3267,8 @@ char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params) {
BOOL_STR(sd_img_gen_params->vae_tiling_params.enabled), BOOL_STR(sd_img_gen_params->vae_tiling_params.enabled),
BOOL_STR(sd_img_gen_params->vae_tiling_params.temporal_tiling), BOOL_STR(sd_img_gen_params->vae_tiling_params.temporal_tiling),
SAFE_STR(sd_img_gen_params->vae_tiling_params.extra_tiling_args), SAFE_STR(sd_img_gen_params->vae_tiling_params.extra_tiling_args),
BOOL_STR(sd_img_gen_params->circular_x),
BOOL_STR(sd_img_gen_params->circular_y),
BOOL_STR(sd_img_gen_params->hires.enabled), BOOL_STR(sd_img_gen_params->hires.enabled),
sd_hires_upscaler_name(sd_img_gen_params->hires.upscaler), sd_hires_upscaler_name(sd_img_gen_params->hires.upscaler),
SAFE_STR(sd_img_gen_params->hires.model_path), SAFE_STR(sd_img_gen_params->hires.model_path),
@ -3336,6 +3317,8 @@ void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params) {
sd_vid_gen_params->hires.upscale_tile_size = 128; sd_vid_gen_params->hires.upscale_tile_size = 128;
sd_vid_gen_params->hires.custom_sigmas = nullptr; sd_vid_gen_params->hires.custom_sigmas = nullptr;
sd_vid_gen_params->hires.custom_sigmas_count = 0; sd_vid_gen_params->hires.custom_sigmas_count = 0;
sd_vid_gen_params->circular_x = false;
sd_vid_gen_params->circular_y = false;
sd_cache_params_init(&sd_vid_gen_params->cache); sd_cache_params_init(&sd_vid_gen_params->cache);
} }
@ -4211,6 +4194,25 @@ struct CircularAxesState {
bool circular_y = false; bool circular_y = false;
}; };
static void apply_circular_axes_to_diffusion(sd_ctx_t* sd_ctx, bool circular_x, bool circular_y) {
sd_ctx->sd->circular_x = circular_x;
sd_ctx->sd->circular_y = circular_y;
if (sd_ctx->sd->diffusion_model) {
sd_ctx->sd->diffusion_model->set_circular_axes(circular_x, circular_y);
}
if (sd_ctx->sd->high_noise_diffusion_model) {
sd_ctx->sd->high_noise_diffusion_model->set_circular_axes(circular_x, circular_y);
}
if (sd_ctx->sd->control_net) {
sd_ctx->sd->control_net->set_circular_axes(circular_x, circular_y);
}
if (circular_x || circular_y) {
LOG_INFO("Using circular padding for convolutions (x=%s, y=%s)",
circular_x ? "true" : "false",
circular_y ? "true" : "false");
}
}
static CircularAxesState configure_image_vae_axes(sd_ctx_t* sd_ctx, static CircularAxesState configure_image_vae_axes(sd_ctx_t* sd_ctx,
const sd_img_gen_params_t* sd_img_gen_params, const sd_img_gen_params_t* sd_img_gen_params,
const GenerationRequest& request) { const GenerationRequest& request) {
@ -4967,6 +4969,7 @@ SD_API bool generate_image(sd_ctx_t* sd_ctx,
sd_ctx->sd->sampler_rng->manual_seed(request.seed); sd_ctx->sd->sampler_rng->manual_seed(request.seed);
sd_ctx->sd->set_flow_shift(sd_img_gen_params->sample_params.flow_shift); sd_ctx->sd->set_flow_shift(sd_img_gen_params->sample_params.flow_shift);
sd_ctx->sd->apply_loras(sd_img_gen_params->loras, sd_img_gen_params->lora_count); sd_ctx->sd->apply_loras(sd_img_gen_params->loras, sd_img_gen_params->lora_count);
apply_circular_axes_to_diffusion(sd_ctx, sd_img_gen_params->circular_x, sd_img_gen_params->circular_y);
ImageVaeAxesGuard axes_guard(sd_ctx, sd_img_gen_params, request); ImageVaeAxesGuard axes_guard(sd_ctx, sd_img_gen_params, request);
@ -5805,6 +5808,7 @@ SD_API bool generate_video(sd_ctx_t* sd_ctx,
} }
int64_t t0 = ggml_time_ms(); int64_t t0 = ggml_time_ms();
sd_ctx->sd->vae_tiling_params = sd_vid_gen_params->vae_tiling_params; sd_ctx->sd->vae_tiling_params = sd_vid_gen_params->vae_tiling_params;
apply_circular_axes_to_diffusion(sd_ctx, sd_vid_gen_params->circular_x, sd_vid_gen_params->circular_y);
GenerationRequest request(sd_ctx, sd_vid_gen_params); GenerationRequest request(sd_ctx, sd_vid_gen_params);
bool latent_upscale_enabled = request.hires.enabled; bool latent_upscale_enabled = request.hires.enabled;
GenerationRequest hires_request = request; GenerationRequest hires_request = request;