mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-18 05:50:10 +08:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e16d26a96a | ||
|
|
a4a966990d | ||
|
|
a1ded76da5 | ||
|
|
8cc5ad8f1c | ||
|
|
4a892293ac | ||
|
|
fd43b542a6 | ||
|
|
0963357214 |
@@ -139,7 +139,7 @@ void example_log_printf(sd_log_level_t level, const char* file, int line, const
|
||||
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
message += " - " + sd_basename(file) + ":" + std::to_string(line) + '\n';
|
||||
message += " --- " + sd_basename(file) + ":" + std::to_string(line) + '\n';
|
||||
|
||||
log_print(level, message.c_str(), log_level, log_color);
|
||||
}
|
||||
|
||||
+16
-1
@@ -1095,7 +1095,8 @@ Response fields:
|
||||
Compared with `img_gen`, the `vid_gen` request body:
|
||||
|
||||
- `vid_gen` is a single video sequence job, so `batch_count` is not part of the request schema
|
||||
- `ref_images`, `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `ref_images` is accepted for MiniMax-H3 Ref2VA conditioning; other video model families currently ignore it
|
||||
- `vid_gen` adds `end_image`, `control_frames`, `high_noise_sample_params`, `video_frames`, `fps`, `moe_boundary`, and `vace_strength`
|
||||
|
||||
Example:
|
||||
@@ -1116,6 +1117,7 @@ Example:
|
||||
|
||||
"init_image": null,
|
||||
"end_image": null,
|
||||
"ref_images": [],
|
||||
"control_frames": [],
|
||||
|
||||
"sample_params": {
|
||||
@@ -1185,6 +1187,17 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
### Reference Image Rules
|
||||
|
||||
- `ref_images` contains reference images for MiniMax-H3 Ref2VA conditioning.
|
||||
- Images retain request order and correspond to `<Picture 1>`, `<Picture 2>`, and so on in the prompt.
|
||||
- For MiniMax-H3, non-empty `ref_images` cannot be combined with `init_image` or `end_image`. A conflicting API request fails during generation; the WebUI checks this before submission.
|
||||
- MiniMax-H3 does not support `control_frames`; leave that array empty.
|
||||
- Other video model families currently ignore `ref_images`.
|
||||
- `features_by_mode.vid_gen` does not currently advertise `ref_images`; its absence is not an indication that MiniMax-H3 reference images are unsupported.
|
||||
|
||||
See [MiniMax-H3 reference conditioning](../../docs/minimax_h3.md#reference-to-audio-video-conditioning) for model requirements and prompt examples.
|
||||
|
||||
### LoRA Rules
|
||||
|
||||
- The server only accepts explicit LoRA entries from the `lora` field.
|
||||
@@ -1202,6 +1215,7 @@ Channel expectations:
|
||||
|
||||
- `init_image`: 3 channels
|
||||
- `end_image`: 3 channels
|
||||
- `ref_images[]`: decoded with native channels, then converted to RGB by MiniMax-H3
|
||||
- `control_frames[]`: 3 channels
|
||||
|
||||
Frame ordering rules:
|
||||
@@ -1240,6 +1254,7 @@ Image and frame fields:
|
||||
| --- | --- |
|
||||
| `init_image` | `string \| null` |
|
||||
| `end_image` | `string \| null` |
|
||||
| `ref_images` | `array<string>` |
|
||||
| `control_frames` | `array<string>` |
|
||||
|
||||
LoRA fields:
|
||||
|
||||
+1
-1
Submodule examples/server/frontend updated: dd74a8e808...8e7c115059
@@ -75,6 +75,8 @@ int main(int argc, const char** argv) {
|
||||
}
|
||||
SDSvrParams svr_params;
|
||||
SDContextParams ctx_params;
|
||||
// Preserve the CLI default while disabling cwd scans for sd-server.
|
||||
ctx_params.lora_model_dir.clear();
|
||||
SDGenerationParams default_gen_params;
|
||||
|
||||
sd_set_log_callback(sd_log_cb, (void*)&svr_params);
|
||||
|
||||
@@ -254,6 +254,12 @@ std::string SDSvrParams::to_string() const {
|
||||
void refresh_lora_cache(ServerRuntime& rt) {
|
||||
std::vector<LoraEntry> new_cache;
|
||||
|
||||
if (rt.ctx_params->lora_model_dir.empty()) {
|
||||
std::lock_guard<std::mutex> lock(*rt.lora_mutex);
|
||||
rt.lora_cache->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
fs::path lora_dir = rt.ctx_params->lora_model_dir;
|
||||
std::error_code ec;
|
||||
if (fs::exists(lora_dir, ec) && !ec && fs::is_directory(lora_dir, ec) && !ec) {
|
||||
|
||||
@@ -399,7 +399,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -817,7 +817,7 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -909,6 +909,31 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
|
||||
size_t chunk_count = std::max(std::max(clip_l_tokens.size(), clip_g_tokens.size()), t5_tokens.size()) / chunk_len;
|
||||
|
||||
// Each active encoder must cover every chunk of the longest token sequence.
|
||||
Tokenizer* tokenizers[] = {
|
||||
clip_l ? clip_l_tokenizer.get() : nullptr,
|
||||
clip_g ? clip_g_tokenizer.get() : nullptr,
|
||||
t5 ? &t5_tokenizer : nullptr,
|
||||
};
|
||||
const size_t target_length = chunk_count * chunk_len;
|
||||
|
||||
for (size_t encoder_idx = 0; encoder_idx < 3; encoder_idx++) {
|
||||
auto& tokens = token_and_weights[encoder_idx].first;
|
||||
auto& weights = token_and_weights[encoder_idx].second;
|
||||
if (!tokenizers[encoder_idx] || tokens.size() >= target_length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<int> empty_tokens;
|
||||
std::vector<float> empty_weights;
|
||||
tokenizers[encoder_idx]->pad_tokens(empty_tokens, &empty_weights, nullptr, chunk_len, chunk_len, false);
|
||||
|
||||
while (tokens.size() < target_length) {
|
||||
tokens.insert(tokens.end(), empty_tokens.begin(), empty_tokens.end());
|
||||
weights.insert(weights.end(), empty_weights.begin(), empty_weights.end());
|
||||
}
|
||||
}
|
||||
|
||||
for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
|
||||
// clip_l
|
||||
sd::Tensor<float> chunk_hidden_states_l;
|
||||
@@ -1207,7 +1232,7 @@ struct FluxCLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1484,7 +1509,7 @@ struct T5CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1873,7 +1898,7 @@ struct AnimaConditioner : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> qwen_tokens;
|
||||
@@ -2144,7 +2169,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> tokens;
|
||||
|
||||
+11
-11
@@ -223,13 +223,13 @@ ggml_tensor* ggml_ext_linear(ggml_context* ctx,
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
}
|
||||
if (scale != 1.f) {
|
||||
@@ -488,7 +488,7 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
|
||||
x = ggml_mul_mat(ctx,
|
||||
ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]),
|
||||
ggml_reshape_2d(ctx, w, w->ne[0] * w->ne[1] * w->ne[2] * IC, OC));
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
|
||||
int64_t OD = im2col->ne[3] / N;
|
||||
x = ggml_reshape_4d(ctx, x, im2col->ne[1] * im2col->ne[2], OD, N, OC);
|
||||
@@ -683,8 +683,8 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in->type == GGML_TYPE_F32 && sd_backend_supports_cuda_mma(backend);
|
||||
if (pad_head) {
|
||||
// CUDA FA MMA starts at 64 channels; keep the original head's attention scale.
|
||||
q_in = ggml_pad(ctx, q_in, 64 - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, 64 - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -694,7 +694,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v_in, 0, 2, 1, 3));
|
||||
v_in = ggml_reshape_3d(ctx, v_in, d_head, L_k, n_kv_head * N);
|
||||
if (pad_head) {
|
||||
v_in = ggml_pad(ctx, v_in, 64 - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
|
||||
@@ -721,7 +721,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
if (!ggml_backend_supports_op(backend, out)) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_flash_attn_ext_set_prec(out, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(out, GGML_PREC_F32);
|
||||
if (kv_scale != 1.0f) {
|
||||
out = ggml_ext_scale(ctx, out, 1.0f / kv_scale);
|
||||
}
|
||||
@@ -742,9 +742,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
}
|
||||
if (padded_head != d_head) {
|
||||
// Keep the original head's softmax scale when padding for the CUDA kernel.
|
||||
q_in = ggml_pad(ctx, q_in, padded_head - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, padded_head - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, padded_head - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -797,7 +797,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v = ggml_reshape_3d(ctx, v, L_k, d_head, n_kv_head * N); // [N * n_kv_head, d_head, L_k]
|
||||
|
||||
auto kq = ggml_mul_mat(ctx, k, q); // [N * n_head, L_q, L_k]
|
||||
ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(kq, GGML_PREC_F32);
|
||||
kq = ggml_scale_inplace(ctx, kq, scale);
|
||||
if (mask) {
|
||||
kq = ggml_add_inplace(ctx, kq, mask);
|
||||
|
||||
+16
-1
@@ -62,6 +62,21 @@ void replace_all_chars(std::string& str, char target, char replacement) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string escape_newlines(const std::string& text) {
|
||||
std::string escaped;
|
||||
escaped.reserve(text.size());
|
||||
for (char ch : text) {
|
||||
if (ch == '\n') {
|
||||
escaped += "\\n";
|
||||
} else if (ch == '\r') {
|
||||
escaped += "\\r";
|
||||
} else {
|
||||
escaped += ch;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static std::string sd_vformat(const char* fmt, va_list ap) {
|
||||
char small[128];
|
||||
va_list ap2;
|
||||
@@ -652,7 +667,7 @@ static void sd_log_dispatch(sd_log_level_t level, const std::string& origin, con
|
||||
if (origin == "ggml") {
|
||||
message = origin + ": " + message + '\n';
|
||||
} else {
|
||||
message += " - " + origin + '\n';
|
||||
message += " --- " + origin + '\n';
|
||||
}
|
||||
sd_log_cb(level, message.c_str(), sd_log_cb_data);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ bool contains(const std::string& str, const std::string& substr);
|
||||
std::string sd_format(const char* fmt, ...);
|
||||
|
||||
void replace_all_chars(std::string& str, char target, char replacement);
|
||||
std::string escape_newlines(const std::string& text);
|
||||
|
||||
int round_up_to(int value, int base);
|
||||
|
||||
|
||||
@@ -633,8 +633,11 @@ namespace MiniMaxH3VAE {
|
||||
auto plan = make_vae_temporal_tile_plan(input.shape()[2], {17, 0});
|
||||
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||
SD_UNUSED(tile);
|
||||
return VAE::encode(n_threads, chunk, tiling, circular_x, circular_y);
|
||||
// keep the runner alive across chunks; ending it here would
|
||||
// evict and reload the encoder weights every chunk
|
||||
return VAE::encode(n_threads, chunk, tiling, circular_x, circular_y, false);
|
||||
});
|
||||
runner_end();
|
||||
if (result.empty()) {
|
||||
return {};
|
||||
}
|
||||
@@ -695,13 +698,16 @@ namespace MiniMaxH3VAE {
|
||||
{static_cast<int>(tokens_per_chunk + token_overlap), static_cast<int>(token_overlap)});
|
||||
GGML_ASSERT(plan.tiles.size() == static_cast<size_t>(num_chunks));
|
||||
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||
// keep the runner alive across chunks; ending it here would
|
||||
// evict and reload the decoder weights every chunk
|
||||
auto decoded = VAE::decode(n_threads,
|
||||
chunk,
|
||||
tiling,
|
||||
true,
|
||||
circular_x,
|
||||
circular_y,
|
||||
silent);
|
||||
silent,
|
||||
false);
|
||||
if (decoded.empty()) {
|
||||
return sd::Tensor<float>();
|
||||
}
|
||||
@@ -728,6 +734,7 @@ namespace MiniMaxH3VAE {
|
||||
}
|
||||
return first;
|
||||
});
|
||||
runner_end();
|
||||
if (result.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
+26
-2
@@ -241,6 +241,15 @@ public:
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool circular_x = false,
|
||||
bool circular_y = false) {
|
||||
return encode(n_threads, x, tiling_params, circular_x, circular_y, true);
|
||||
}
|
||||
|
||||
sd::Tensor<float> encode(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool circular_x,
|
||||
bool circular_y,
|
||||
bool auto_end) {
|
||||
int64_t t0 = ggml_time_ms();
|
||||
tiling_params = resolve_tiling_params(tiling_params);
|
||||
sd::Tensor<float> input = x;
|
||||
@@ -280,7 +289,9 @@ public:
|
||||
tiling_params);
|
||||
}
|
||||
|
||||
runner_end();
|
||||
if (auto_end) {
|
||||
runner_end();
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae encode compute failed");
|
||||
@@ -298,6 +309,17 @@ public:
|
||||
bool circular_x = false,
|
||||
bool circular_y = false,
|
||||
bool silent = false) {
|
||||
return decode(n_threads, x, tiling_params, decode_video, circular_x, circular_y, silent, true);
|
||||
}
|
||||
|
||||
sd::Tensor<float> decode(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool decode_video,
|
||||
bool circular_x,
|
||||
bool circular_y,
|
||||
bool silent,
|
||||
bool auto_end) {
|
||||
int64_t t0 = ggml_time_ms();
|
||||
tiling_params = resolve_tiling_params(tiling_params);
|
||||
sd::Tensor<float> input = x;
|
||||
@@ -338,7 +360,9 @@ public:
|
||||
tiling_params);
|
||||
}
|
||||
|
||||
runner_end();
|
||||
if (auto_end) {
|
||||
runner_end();
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae decode compute failed");
|
||||
|
||||
@@ -242,7 +242,7 @@ bool BPETokenizer::encode(const std::string& text, std::vector<int>& result, on_
|
||||
ss << "\"" << token << "\", ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", text.c_str(), bpe_tokens.size(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", escape_newlines(text).c_str(), bpe_tokens.size(), escape_newlines(ss.str()).c_str());
|
||||
result = std::move(bpe_tokens);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ bool HFTokenizer::encode(const std::string& text, std::vector<int>& tokens, on_n
|
||||
}
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", text.c_str(), result.size(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", escape_newlines(text).c_str(), result.size(), escape_newlines(ss.str()).c_str());
|
||||
tokens = std::move(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ bool T5UniGramTokenizer::encode(const std::string& input, std::vector<int>& resu
|
||||
ss << "\"" << token_str << "\", ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to tokens %s", input.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to tokens %s", escape_newlines(input).c_str(), escape_newlines(ss.str()).c_str());
|
||||
|
||||
result = std::move(tokens);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user