mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-11-09 18:36:33 +00:00
feat(server): report generation stages independently of previews (#2303)
Co-authored-by: Maxime Dréan <maximedrean@proton.me>
This commit is contained in:
1 parent
b43ce4badd
commit
fc350be2ce
12 files changed
+259
-11
No files matched your search
@@ -76,6 +76,16 @@ Recipes declaring `video_frames` also switch the form to Video Generation;
|
|||||||
other recipes switch to Image Generation. Generation is enabled after the
|
other recipes switch to Image Generation. Generation is enabled after the
|
||||||
selected recipe is loaded. Changing a selection does not reload the model
|
selected recipe is loaded. Changing a selection does not reload the model
|
||||||
until you click Load.
|
until you click Load.
|
||||||
|
|
||||||
|
The generation status bar shows the current stage and elapsed time even when
|
||||||
|
Preview Mode is `none`. During sampling it shows completed steps for the current
|
||||||
|
pass; prompt encoding and VAE decoding show an activity indicator when their
|
||||||
|
total work is unknown. Sampling reaching 100% does not mean the job is finished:
|
||||||
|
decoding, ADetailer, and output encoding may still follow.
|
||||||
|
The canvas keeps the submitted output aspect ratio, and stage changes keep the
|
||||||
|
same layout. Editing the next request's resolution does not resize the current
|
||||||
|
preview or result.
|
||||||
|
|
||||||
The server can also start with the existing model arguments shown below;
|
The server can also start with the existing model arguments shown below;
|
||||||
existing API clients then work without selecting a recipe. The UI automatically
|
existing API clients then work without selecting a recipe. The UI automatically
|
||||||
selects **Command-line model (all features)** and uses the startup generation
|
selects **Command-line model (all features)** and uses the startup generation
|
||||||
|
|||||||
@@ -775,6 +775,7 @@ Field types:
|
|||||||
| `queue_position` | `integer` |
|
| `queue_position` | `integer` |
|
||||||
| `result` | `object \| null` |
|
| `result` | `object \| null` |
|
||||||
| `error` | `object \| null` |
|
| `error` | `object \| null` |
|
||||||
|
| `progress` | `object` |
|
||||||
| `preview` | `object \| null` |
|
| `preview` | `object \| null` |
|
||||||
|
|
||||||
`created`, `started`, and `completed` are integer Unix timestamps in
|
`created`, `started`, and `completed` are integer Unix timestamps in
|
||||||
@@ -782,6 +783,57 @@ Field types:
|
|||||||
the elapsed generation time in seconds, including output encoding and excluding
|
the elapsed generation time in seconds, including output encoding and excluding
|
||||||
queue time.
|
queue time.
|
||||||
|
|
||||||
|
`progress` is available independently of the preview setting, for both image
|
||||||
|
and video jobs. Continue polling until `status` is terminal: a sampling pass
|
||||||
|
can finish before decoding, ADetailer, or output encoding finishes.
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `progress.stage` | `string` | Current execution stage; see below |
|
||||||
|
| `progress.detail` | `string` | Stage detail, or an empty string |
|
||||||
|
| `progress.pass` | `integer` | Sampling pass number within the job; zero before sampling |
|
||||||
|
| `progress.completed` | `integer \| null` | Completed units in the current stage/pass |
|
||||||
|
| `progress.total` | `integer \| null` | Actual total units, or null when unknown |
|
||||||
|
| `progress.unit` | `string \| null` | `steps` for sampling, `images` for image output encoding; null when uncounted |
|
||||||
|
| `progress.stage_elapsed_ms` | `integer` | Elapsed time in the current stage/detail/pass |
|
||||||
|
| `progress.elapsed_ms` | `integer` | Job execution time, excluding queue time; zero before execution |
|
||||||
|
|
||||||
|
Stages include `queued`, `waiting` (waiting for the execution lock), `preparing`,
|
||||||
|
`conditioning`, `sampling`, `vae_encode`, `vae_decode`, `audio_decode`,
|
||||||
|
`upscaling`, `adetailer`, and `encoding`. Stages may repeat or be skipped.
|
||||||
|
Details include `positive_prompt` / `negative_prompt` / `image_condition` for
|
||||||
|
conditioning, `high_noise` / `low_noise` for two-stage sampling, and `image` /
|
||||||
|
`video` for output encoding. During ADetailer, `detail` identifies the inner
|
||||||
|
stage, such as `conditioning` or `sampling`. Clients should tolerate unknown
|
||||||
|
stage, detail, and unit values.
|
||||||
|
|
||||||
|
Sampling counts completed logical steps using the actual schedule, including
|
||||||
|
strength adjustments. Each new sampling pass increments `pass` and starts at
|
||||||
|
zero, including batch items, highres passes, high/low-noise passes, and ADetailer
|
||||||
|
passes. The ratio is **current-pass progress**, not overall job completion.
|
||||||
|
The progress snapshot is published atomically. Preview metadata still describes
|
||||||
|
the preview frame, which may be older than the current progress.
|
||||||
|
|
||||||
|
Unknown totals are represented by null counts, not an estimated percentage.
|
||||||
|
For example, prompt encoding can report:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"progress": {
|
||||||
|
"stage": "conditioning",
|
||||||
|
"detail": "positive_prompt",
|
||||||
|
"pass": 0,
|
||||||
|
"completed": null,
|
||||||
|
"total": null,
|
||||||
|
"unit": null,
|
||||||
|
"stage_elapsed_ms": 3200,
|
||||||
|
"elapsed_ms": 4100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Elapsed times freeze when a job completes, fails, or is cancelled; the last
|
||||||
|
stage is retained for inspection. Use `status` to determine the final outcome.
|
||||||
|
Elapsed time alone does not indicate that computation is advancing.
|
||||||
|
|
||||||
`preview` sub-fields:
|
`preview` sub-fields:
|
||||||
|
|
||||||
| Field | Type | Notes |
|
| Field | Type | Notes |
|
||||||
|
|||||||
@@ -92,6 +92,63 @@ static void set_preview_callback(AsyncGenerationJob& job) {
|
|||||||
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
|
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_job_progress(AsyncGenerationJob& job, const char* stage, const char* detail = "", int sample_pass = -1, int64_t completed = 0, int64_t total = 0, const char* unit = "") {
|
||||||
|
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||||
|
auto& progress = job.progress;
|
||||||
|
if (sample_pass < 0) {
|
||||||
|
sample_pass = progress.sample_pass;
|
||||||
|
}
|
||||||
|
if (progress.stage != stage || progress.detail != detail || progress.sample_pass != sample_pass) {
|
||||||
|
progress.stage_started = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
progress.stage = stage;
|
||||||
|
progress.detail = detail;
|
||||||
|
progress.sample_pass = sample_pass;
|
||||||
|
progress.completed = completed;
|
||||||
|
progress.total = total;
|
||||||
|
progress.unit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void finish_job_progress(AsyncGenerationJob& job) {
|
||||||
|
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||||
|
job.progress.finished = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
class JobCallbacksGuard {
|
||||||
|
sd_ctx_t* ctx_;
|
||||||
|
AsyncGenerationJob& job_;
|
||||||
|
bool detailing_ = false;
|
||||||
|
|
||||||
|
static void progress_callback(const sd_generation_progress_t* progress, void* data) {
|
||||||
|
auto& guard = *static_cast<JobCallbacksGuard*>(data);
|
||||||
|
update_job_progress(guard.job_, guard.detailing_ ? "adetailer" : progress->stage,
|
||||||
|
guard.detailing_ ? progress->stage : progress->detail,
|
||||||
|
progress->sample_pass, progress->completed, progress->total, progress->unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
JobCallbacksGuard(sd_ctx_t* ctx, AsyncGenerationJob& job)
|
||||||
|
: ctx_(ctx), job_(job) {
|
||||||
|
clear_preview_callback();
|
||||||
|
set_preview_callback(job);
|
||||||
|
sd_set_generation_progress_callback(ctx_, progress_callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~JobCallbacksGuard() {
|
||||||
|
sd_set_generation_progress_callback(ctx_, nullptr, nullptr);
|
||||||
|
clear_preview_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_detailing() {
|
||||||
|
clear_preview_callback();
|
||||||
|
detailing_ = true;
|
||||||
|
update_job_progress(job_, "adetailer");
|
||||||
|
}
|
||||||
|
|
||||||
|
JobCallbacksGuard(const JobCallbacksGuard&) = delete;
|
||||||
|
JobCallbacksGuard& operator=(const JobCallbacksGuard&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
const char* async_job_kind_name(AsyncJobKind kind) {
|
const char* async_job_kind_name(AsyncJobKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case AsyncJobKind::ImgGen:
|
case AsyncJobKind::ImgGen:
|
||||||
@@ -186,6 +243,7 @@ bool cancel_queued_job(AsyncJobManager& manager, AsyncGenerationJob& job) {
|
|||||||
job.result_fps = 0;
|
job.result_fps = 0;
|
||||||
job.error_code = "cancelled";
|
job.error_code = "cancelled";
|
||||||
job.error_message = "job cancelled by client";
|
job.error_message = "job cancelled by client";
|
||||||
|
finish_job_progress(job);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +258,24 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
|||||||
result["completed"] = job.completed_at == 0 ? json(nullptr) : json(job.completed_at);
|
result["completed"] = job.completed_at == 0 ? json(nullptr) : json(job.completed_at);
|
||||||
result["queue_position"] = 0;
|
result["queue_position"] = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(job.progress_mutex);
|
||||||
|
const auto& progress = job.progress;
|
||||||
|
const auto end = progress.finished == std::chrono::steady_clock::time_point{}
|
||||||
|
? std::chrono::steady_clock::now()
|
||||||
|
: progress.finished;
|
||||||
|
result["progress"] = {
|
||||||
|
{"stage", progress.stage},
|
||||||
|
{"detail", progress.detail},
|
||||||
|
{"pass", progress.sample_pass},
|
||||||
|
{"completed", progress.total > 0 ? json(progress.completed) : json(nullptr)},
|
||||||
|
{"total", progress.total > 0 ? json(progress.total) : json(nullptr)},
|
||||||
|
{"unit", progress.unit.empty() ? json(nullptr) : json(progress.unit)},
|
||||||
|
{"stage_elapsed_ms", std::chrono::duration_cast<std::chrono::milliseconds>(end - progress.stage_started).count()},
|
||||||
|
{"elapsed_ms", job.started_at == 0 ? 0 : std::max<int64_t>(0, (job.completed_at == 0 ? unix_timestamp_now_ms() : job.completed_at) - job.started_at)},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (job.status == AsyncJobStatus::Queued) {
|
if (job.status == AsyncJobStatus::Queued) {
|
||||||
size_t position = 1;
|
size_t position = 1;
|
||||||
for (const auto& queued_id : manager.queue) {
|
for (const auto& queued_id : manager.queue) {
|
||||||
@@ -278,17 +354,19 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||||
set_preview_callback(job);
|
JobCallbacksGuard callbacks(runtime.sd_ctx, job);
|
||||||
sd_image_generation_result_t* generated = nullptr;
|
sd_image_generation_result_t* generated = nullptr;
|
||||||
generate_image(runtime.sd_ctx, ¶ms, &generated);
|
generate_image(runtime.sd_ctx, ¶ms, &generated);
|
||||||
results.adopt(generated, runtime.ctx_params->lora_model_dir);
|
results.adopt(generated, runtime.ctx_params->lora_model_dir);
|
||||||
clear_preview_callback();
|
|
||||||
if (job.cancel_requested.load()) {
|
if (job.cancel_requested.load()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!results.empty() && !apply_server_adetailer(runtime, job.img_gen, params, results)) {
|
if (!results.empty() && job.img_gen.adetailer_enabled && !runtime.default_gen_params->ad_model_path.empty()) {
|
||||||
error_message = "ADetailer failed";
|
callbacks.begin_detailing();
|
||||||
return false;
|
if (!apply_server_adetailer(runtime, job.img_gen, params, results)) {
|
||||||
|
error_message = "ADetailer failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +383,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
|||||||
encoded_format = EncodedImageFormat::WEBP;
|
encoded_format = EncodedImageFormat::WEBP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_job_progress(job, "encoding", "image", -1, 0, num_results, "images");
|
||||||
for (int i = 0; i < num_results; ++i) {
|
for (int i = 0; i < num_results; ++i) {
|
||||||
if (results[i].data == nullptr) {
|
if (results[i].data == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
@@ -325,6 +404,7 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
|||||||
}
|
}
|
||||||
output_images.push_back(base64_encode(image_bytes));
|
output_images.push_back(base64_encode(image_bytes));
|
||||||
output_info.push_back(json::parse(results.generation_info(i)));
|
output_info.push_back(json::parse(results.generation_info(i)));
|
||||||
|
update_job_progress(job, "encoding", "image", -1, i + 1, num_results, "images");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output_images.empty()) {
|
if (output_images.empty()) {
|
||||||
@@ -356,7 +436,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
JobCancellationMonitor cancellation(runtime.sd_ctx, job);
|
||||||
set_preview_callback(job);
|
JobCallbacksGuard callbacks(runtime.sd_ctx, job);
|
||||||
sd_video_generation_result_t* generated = nullptr;
|
sd_video_generation_result_t* generated = nullptr;
|
||||||
if (generate_video(runtime.sd_ctx, ¶ms, &generated)) {
|
if (generate_video(runtime.sd_ctx, ¶ms, &generated)) {
|
||||||
output_fps = generated->fps;
|
output_fps = generated->fps;
|
||||||
@@ -367,7 +447,6 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
|||||||
if (job.vid_gen.gen_params.embed_image_metadata)
|
if (job.vid_gen.gen_params.embed_image_metadata)
|
||||||
str_params = results.generation_info(0);
|
str_params = results.generation_info(0);
|
||||||
}
|
}
|
||||||
clear_preview_callback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
num_results = results.count();
|
num_results = results.count();
|
||||||
@@ -377,6 +456,7 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_job_progress(job, "encoding", "video");
|
||||||
std::vector<uint8_t> video_bytes = create_video_from_sd_images_to_vector(job.vid_gen.output_format,
|
std::vector<uint8_t> video_bytes = create_video_from_sd_images_to_vector(job.vid_gen.output_format,
|
||||||
results.data(),
|
results.data(),
|
||||||
num_results,
|
num_results,
|
||||||
@@ -425,6 +505,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
|||||||
job = it->second;
|
job = it->second;
|
||||||
job->status = AsyncJobStatus::Generating;
|
job->status = AsyncJobStatus::Generating;
|
||||||
job->started_at = unix_timestamp_now_ms();
|
job->started_at = unix_timestamp_now_ms();
|
||||||
|
update_job_progress(*job, "waiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> output_images;
|
std::vector<std::string> output_images;
|
||||||
@@ -459,6 +540,7 @@ void async_job_worker(ServerRuntime& runtime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
job->completed_at = unix_timestamp_now_ms();
|
job->completed_at = unix_timestamp_now_ms();
|
||||||
|
finish_job_progress(*job);
|
||||||
if (job->cancel_requested.load()) {
|
if (job->cancel_requested.load()) {
|
||||||
job->status = AsyncJobStatus::Cancelled;
|
job->status = AsyncJobStatus::Cancelled;
|
||||||
job->error_code = "cancelled";
|
job->error_code = "cancelled";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
@@ -29,6 +30,17 @@ enum class AsyncJobStatus {
|
|||||||
const char* async_job_kind_name(AsyncJobKind kind);
|
const char* async_job_kind_name(AsyncJobKind kind);
|
||||||
const char* async_job_status_name(AsyncJobStatus status);
|
const char* async_job_status_name(AsyncJobStatus status);
|
||||||
|
|
||||||
|
struct AsyncJobProgress {
|
||||||
|
std::string stage = "queued";
|
||||||
|
std::string detail;
|
||||||
|
std::string unit;
|
||||||
|
int sample_pass = 0;
|
||||||
|
int64_t completed = 0;
|
||||||
|
int64_t total = 0;
|
||||||
|
std::chrono::steady_clock::time_point stage_started = std::chrono::steady_clock::now();
|
||||||
|
std::chrono::steady_clock::time_point finished{};
|
||||||
|
};
|
||||||
|
|
||||||
struct AsyncGenerationJob {
|
struct AsyncGenerationJob {
|
||||||
std::string id;
|
std::string id;
|
||||||
AsyncJobKind kind = AsyncJobKind::ImgGen;
|
AsyncJobKind kind = AsyncJobKind::ImgGen;
|
||||||
@@ -47,6 +59,8 @@ struct AsyncGenerationJob {
|
|||||||
int result_fps = 0;
|
int result_fps = 0;
|
||||||
std::string error_code;
|
std::string error_code;
|
||||||
std::string error_message;
|
std::string error_message;
|
||||||
|
mutable std::mutex progress_mutex;
|
||||||
|
AsyncJobProgress progress;
|
||||||
mutable std::mutex preview_mutex;
|
mutable std::mutex preview_mutex;
|
||||||
std::string preview_b64;
|
std::string preview_b64;
|
||||||
int preview_pass = 0;
|
int preview_pass = 0;
|
||||||
|
|||||||
+1
-1
Submodule examples/server/frontend updated: fa93429c47...d3d83992ff.
@@ -500,6 +500,15 @@ struct ggml_tensor;
|
|||||||
|
|
||||||
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
|
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
|
||||||
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
|
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
|
||||||
|
typedef struct {
|
||||||
|
const char* stage;
|
||||||
|
const char* detail;
|
||||||
|
const char* unit;
|
||||||
|
int sample_pass;
|
||||||
|
int64_t completed;
|
||||||
|
int64_t total;
|
||||||
|
} sd_generation_progress_t;
|
||||||
|
typedef void (*sd_generation_progress_cb_t)(const sd_generation_progress_t* progress, void* data);
|
||||||
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
|
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -510,6 +519,11 @@ typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void*
|
|||||||
|
|
||||||
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
||||||
SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
||||||
|
// Context-local generation events, independent of preview and the legacy progress callback.
|
||||||
|
// Strings and the event are valid only during the synchronous callback. Callbacks must not throw.
|
||||||
|
// total == 0 means unknown; sample_pass counts from 1 since callback registration.
|
||||||
|
// Register/clear only while the context is idle. Passing nullptr clears the callback.
|
||||||
|
SD_API void sd_set_generation_progress_callback(sd_ctx_t* ctx, sd_generation_progress_cb_t cb, void* data);
|
||||||
// In each sampling pass, a positive interval previews every Nth denoiser step, while a
|
// In each sampling pass, a positive interval previews every Nth denoiser step, while a
|
||||||
// negative interval previews only completed logical step -interval. Zero previews the final
|
// negative interval previews only completed logical step -interval. Zero previews the final
|
||||||
// completed step of the first sampling pass (base-resolution or high-noise).
|
// completed step of the first sampling pass (base-resolution or high-noise).
|
||||||
|
|||||||
@@ -1807,6 +1807,7 @@ void StableDiffusionGGML::lora_stat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool StableDiffusionGGML::apply_loras(const sd_lora_t* loras, uint32_t lora_count) {
|
bool StableDiffusionGGML::apply_loras(const sd_lora_t* loras, uint32_t lora_count) {
|
||||||
|
generation_progress.report("preparing", "loras");
|
||||||
std::vector<ModelManager::LoraSpec> all_loras;
|
std::vector<ModelManager::LoraSpec> all_loras;
|
||||||
all_loras.reserve(lora_count);
|
all_loras.reserve(lora_count);
|
||||||
for (uint32_t i = 0; i < lora_count; i++) {
|
for (uint32_t i = 0; i < lora_count; i++) {
|
||||||
@@ -2243,6 +2244,7 @@ void StableDiffusionGGML::report_sample_progress(int step,
|
|||||||
? (now - *last_progress_us) / 1000000.f
|
? (now - *last_progress_us) / 1000000.f
|
||||||
: 0.f;
|
: 0.f;
|
||||||
pretty_progress(showstep, (int)total_steps, step_seconds);
|
pretty_progress(showstep, (int)total_steps, step_seconds);
|
||||||
|
generation_progress.report_sample(showstep, static_cast<int64_t>(total_steps));
|
||||||
if (last_progress_us != nullptr) {
|
if (last_progress_us != nullptr) {
|
||||||
*last_progress_us = now;
|
*last_progress_us = now;
|
||||||
}
|
}
|
||||||
@@ -2424,6 +2426,9 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
|||||||
|
|
||||||
int64_t last_progress_us = ggml_time_us();
|
int64_t last_progress_us = ggml_time_us();
|
||||||
sd_begin_preview_pass(static_cast<int>(steps));
|
sd_begin_preview_pass(static_cast<int>(steps));
|
||||||
|
generation_progress.begin_sample(static_cast<int64_t>(steps),
|
||||||
|
work_diffusion_model == high_noise_diffusion_model ? "high_noise" : high_noise_diffusion_model != nullptr ? "low_noise"
|
||||||
|
: "");
|
||||||
SamplePreviewContext preview = prepare_sample_preview_context();
|
SamplePreviewContext preview = prepare_sample_preview_context();
|
||||||
|
|
||||||
sd::Tensor<float> processed_init_latent = denoiser->process_latent_in(init_latent);
|
sd::Tensor<float> processed_init_latent = denoiser->process_latent_in(init_latent);
|
||||||
@@ -2995,6 +3000,7 @@ int StableDiffusionGGML::align_video_frames(int frames) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sd::Tensor<float> StableDiffusionGGML::encode_to_vae_latents(const sd::Tensor<float>& x) {
|
sd::Tensor<float> StableDiffusionGGML::encode_to_vae_latents(const sd::Tensor<float>& x) {
|
||||||
|
generation_progress.report("vae_encode");
|
||||||
auto latents = first_stage_model->encode(n_threads, x, vae_tiling_params, circular_x, circular_y);
|
auto latents = first_stage_model->encode(n_threads, x, vae_tiling_params, circular_x, circular_y);
|
||||||
if (latents.empty()) {
|
if (latents.empty()) {
|
||||||
return {};
|
return {};
|
||||||
@@ -3015,6 +3021,7 @@ sd::Tensor<float> StableDiffusionGGML::encode_first_stage(const sd::Tensor<float
|
|||||||
}
|
}
|
||||||
|
|
||||||
sd::Tensor<float> StableDiffusionGGML::decode_first_stage(const sd::Tensor<float>& x, bool decode_video) {
|
sd::Tensor<float> StableDiffusionGGML::decode_first_stage(const sd::Tensor<float>& x, bool decode_video) {
|
||||||
|
generation_progress.report("vae_decode");
|
||||||
if (sd_version_is_pid(version) || sd_version_is_minit2i(version) || version == VERSION_IRIS) {
|
if (sd_version_is_pid(version) || sd_version_is_minit2i(version) || version == VERSION_IRIS) {
|
||||||
return sd::ops::clamp((x + 1.f) * 0.5f, 0.0f, 1.0f);
|
return sd::ops::clamp((x + 1.f) * 0.5f, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
@@ -3069,6 +3076,7 @@ sd::Tensor<float> StableDiffusionGGML::decode_ltx_audio_latent(const sd::Tensor<
|
|||||||
if (audio_vae_model == nullptr || audio_latent.empty()) {
|
if (audio_vae_model == nullptr || audio_latent.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
generation_progress.report("audio_decode");
|
||||||
auto waveform = audio_vae_model->decode(n_threads, audio_latent);
|
auto waveform = audio_vae_model->decode(n_threads, audio_latent);
|
||||||
return waveform;
|
return waveform;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "model/adapter/lora.hpp"
|
#include "model/adapter/lora.hpp"
|
||||||
#include "model_builders.h"
|
#include "model_builders.h"
|
||||||
#include "model_manager.h"
|
#include "model_manager.h"
|
||||||
|
#include "runtime/generation_progress.h"
|
||||||
#include "stable-diffusion.h"
|
#include "stable-diffusion.h"
|
||||||
|
|
||||||
class RNG;
|
class RNG;
|
||||||
@@ -199,6 +200,7 @@ public:
|
|||||||
std::unique_ptr<ConditioningCache> conditioning_cache_;
|
std::unique_ptr<ConditioningCache> conditioning_cache_;
|
||||||
std::vector<ModelManager::LoraSpec> conditioning_loras_;
|
std::vector<ModelManager::LoraSpec> conditioning_loras_;
|
||||||
bool executing_ = false;
|
bool executing_ = false;
|
||||||
|
sd::GenerationProgress generation_progress;
|
||||||
|
|
||||||
std::shared_ptr<Denoiser> denoiser;
|
std::shared_ptr<Denoiser> denoiser;
|
||||||
std::vector<float> file_alphas_cumprod;
|
std::vector<float> file_alphas_cumprod;
|
||||||
@@ -254,6 +256,9 @@ public:
|
|||||||
|
|
||||||
explicit ExecutionScope(StableDiffusionGGML& sd)
|
explicit ExecutionScope(StableDiffusionGGML& sd)
|
||||||
: operation(sd) {
|
: operation(sd) {
|
||||||
|
if (operation.acquired) {
|
||||||
|
sd.generation_progress.report("preparing");
|
||||||
|
}
|
||||||
ready = operation.acquired && sd.refresh_model_sources();
|
ready = operation.acquired && sd.refresh_model_sources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -531,6 +531,7 @@ namespace sd::pipeline {
|
|||||||
|
|
||||||
condition_params.ref_image_params = ref_image_params;
|
condition_params.ref_image_params = ref_image_params;
|
||||||
|
|
||||||
|
sd->generation_progress.report("conditioning", "extensions");
|
||||||
sd->prepare_generation_extensions(request->pm_params,
|
sd->prepare_generation_extensions(request->pm_params,
|
||||||
request->pulid_params,
|
request->pulid_params,
|
||||||
condition_params,
|
condition_params,
|
||||||
@@ -538,7 +539,8 @@ namespace sd::pipeline {
|
|||||||
sd->compute_ip_adapter_tokens(sd_img_gen_params->ip_adapter_image, sd_img_gen_params->ip_adapter_strength);
|
sd->compute_ip_adapter_tokens(sd_img_gen_params->ip_adapter_image, sd_img_gen_params->ip_adapter_strength);
|
||||||
int64_t prepare_start_ms = ggml_time_ms();
|
int64_t prepare_start_ms = ggml_time_ms();
|
||||||
condition_params.zero_out_masked = false;
|
condition_params.zero_out_masked = false;
|
||||||
auto cond = sd->get_learned_condition(condition_params);
|
sd->generation_progress.report("conditioning", "positive_prompt");
|
||||||
|
auto cond = sd->get_learned_condition(condition_params);
|
||||||
if (cond.empty()) {
|
if (cond.empty()) {
|
||||||
LOG_ERROR("failed to encode prompt");
|
LOG_ERROR("failed to encode prompt");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -584,6 +586,7 @@ namespace sd::pipeline {
|
|||||||
// Keep source latents in CFG, but omit source images from the text encoder.
|
// Keep source latents in CFG, but omit source images from the text encoder.
|
||||||
condition_params.ref_images = nullptr;
|
condition_params.ref_images = nullptr;
|
||||||
}
|
}
|
||||||
|
sd->generation_progress.report("conditioning", "negative_prompt");
|
||||||
uncond = sd->get_learned_condition(condition_params);
|
uncond = sd->get_learned_condition(condition_params);
|
||||||
if (uncond.empty()) {
|
if (uncond.empty()) {
|
||||||
LOG_ERROR("failed to encode negative prompt");
|
LOG_ERROR("failed to encode negative prompt");
|
||||||
@@ -615,6 +618,7 @@ namespace sd::pipeline {
|
|||||||
if (use_ref_latent_img_cfg) {
|
if (use_ref_latent_img_cfg) {
|
||||||
condition_params.ref_images = &empty_ref_images;
|
condition_params.ref_images = &empty_ref_images;
|
||||||
}
|
}
|
||||||
|
sd->generation_progress.report("conditioning", "image_condition");
|
||||||
img_uncond = sd->get_learned_condition(condition_params);
|
img_uncond = sd->get_learned_condition(condition_params);
|
||||||
if (img_uncond.empty()) {
|
if (img_uncond.empty()) {
|
||||||
LOG_ERROR("failed to encode image guidance prompt");
|
LOG_ERROR("failed to encode image guidance prompt");
|
||||||
@@ -830,6 +834,7 @@ namespace sd::pipeline {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sd->generation_progress.report("upscaling", "hires");
|
||||||
sd::Tensor<float> upscaled_tensor;
|
sd::Tensor<float> upscaled_tensor;
|
||||||
if (request.hires.upscaler == SD_HIRES_UPSCALER_MODEL) {
|
if (request.hires.upscaler == SD_HIRES_UPSCALER_MODEL) {
|
||||||
upscaled_tensor = upscaler->upscale_tensor(decoded);
|
upscaled_tensor = upscaler->upscale_tensor(decoded);
|
||||||
@@ -930,6 +935,7 @@ namespace sd::pipeline {
|
|||||||
ImageVaeAxesGuard axes_guard(sd, sd_img_gen_params, request);
|
ImageVaeAxesGuard axes_guard(sd, sd_img_gen_params, request);
|
||||||
|
|
||||||
SamplePlan plan(sd, sd_img_gen_params, request);
|
SamplePlan plan(sd, sd_img_gen_params, request);
|
||||||
|
sd->generation_progress.report("preparing", "inputs");
|
||||||
auto latents_opt = prepare_image_generation_latents(sd,
|
auto latents_opt = prepare_image_generation_latents(sd,
|
||||||
sd_img_gen_params,
|
sd_img_gen_params,
|
||||||
&request,
|
&request,
|
||||||
@@ -1027,6 +1033,7 @@ namespace sd::pipeline {
|
|||||||
}
|
}
|
||||||
LOG_INFO("hires fix: upscaling to %dx%d", request.hires.target_width, request.hires.target_height);
|
LOG_INFO("hires fix: upscaling to %dx%d", request.hires.target_width, request.hires.target_height);
|
||||||
|
|
||||||
|
sd->generation_progress.report("upscaling", "hires");
|
||||||
std::unique_ptr<UpscalerGGML> hires_upscaler;
|
std::unique_ptr<UpscalerGGML> hires_upscaler;
|
||||||
if (request.hires.upscaler == SD_HIRES_UPSCALER_MODEL) {
|
if (request.hires.upscaler == SD_HIRES_UPSCALER_MODEL) {
|
||||||
if (sd->get_cancel_flag() == SD_CANCEL_ALL) {
|
if (sd->get_cancel_flag() == SD_CANCEL_ALL) {
|
||||||
|
|||||||
@@ -1210,7 +1210,8 @@ namespace sd::pipeline {
|
|||||||
condition_params.ref_image_params = ref_image_params;
|
condition_params.ref_image_params = ref_image_params;
|
||||||
|
|
||||||
int64_t prepare_start_ms = ggml_time_ms();
|
int64_t prepare_start_ms = ggml_time_ms();
|
||||||
embeds.cond = sd->get_learned_condition(condition_params);
|
sd->generation_progress.report("conditioning", "positive_prompt");
|
||||||
|
embeds.cond = sd->get_learned_condition(condition_params);
|
||||||
if (embeds.cond.empty()) {
|
if (embeds.cond.empty()) {
|
||||||
LOG_ERROR("failed to encode video prompt");
|
LOG_ERROR("failed to encode video prompt");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -1235,7 +1236,8 @@ namespace sd::pipeline {
|
|||||||
}
|
}
|
||||||
if (request.use_uncond) {
|
if (request.use_uncond) {
|
||||||
condition_params.text = request.negative_prompt;
|
condition_params.text = request.negative_prompt;
|
||||||
embeds.uncond = sd->get_learned_condition(condition_params);
|
sd->generation_progress.report("conditioning", "negative_prompt");
|
||||||
|
embeds.uncond = sd->get_learned_condition(condition_params);
|
||||||
if (embeds.uncond.empty()) {
|
if (embeds.uncond.empty()) {
|
||||||
LOG_ERROR("failed to encode negative video prompt");
|
LOG_ERROR("failed to encode negative video prompt");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -1901,6 +1903,7 @@ namespace sd::pipeline {
|
|||||||
sd->reset_generation_extensions();
|
sd->reset_generation_extensions();
|
||||||
|
|
||||||
SamplePlan plan(sd, sd_vid_gen_params, request);
|
SamplePlan plan(sd, sd_vid_gen_params, request);
|
||||||
|
sd->generation_progress.report("preparing", "inputs");
|
||||||
auto latent_inputs_opt = prepare_video_generation_latents(sd, sd_vid_gen_params, &request, ref_image_params);
|
auto latent_inputs_opt = prepare_video_generation_latents(sd, sd_vid_gen_params, &request, ref_image_params);
|
||||||
if (!latent_inputs_opt.has_value()) {
|
if (!latent_inputs_opt.has_value()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2029,6 +2032,7 @@ namespace sd::pipeline {
|
|||||||
LOG_ERROR("cancelling generation before latent upscale");
|
LOG_ERROR("cancelling generation before latent upscale");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
sd->generation_progress.report("upscaling", "hires");
|
||||||
int64_t upscale_start = ggml_time_ms();
|
int64_t upscale_start = ggml_time_ms();
|
||||||
sd::Tensor<float> upscaled_latent;
|
sd::Tensor<float> upscaled_latent;
|
||||||
if (sd_version_is_ltxav(sd->version)) {
|
if (sd_version_is_ltxav(sd->version)) {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef __SD_RUNTIME_GENERATION_PROGRESS_H__
|
||||||
|
#define __SD_RUNTIME_GENERATION_PROGRESS_H__
|
||||||
|
|
||||||
|
#include "stable-diffusion.h"
|
||||||
|
|
||||||
|
namespace sd {
|
||||||
|
|
||||||
|
class GenerationProgress {
|
||||||
|
sd_generation_progress_cb_t callback_ = nullptr;
|
||||||
|
void* data_ = nullptr;
|
||||||
|
int sample_pass_ = 0;
|
||||||
|
const char* sample_detail_ = "";
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_callback(sd_generation_progress_cb_t callback, void* data) {
|
||||||
|
callback_ = callback;
|
||||||
|
data_ = data;
|
||||||
|
sample_pass_ = 0;
|
||||||
|
sample_detail_ = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void report(const char* stage, const char* detail = "", int64_t completed = 0, int64_t total = 0, const char* unit = "") const {
|
||||||
|
if (callback_ != nullptr) {
|
||||||
|
const sd_generation_progress_t progress{stage, detail, unit, sample_pass_, completed, total};
|
||||||
|
callback_(&progress, data_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_sample(int64_t total, const char* detail) {
|
||||||
|
++sample_pass_;
|
||||||
|
sample_detail_ = detail;
|
||||||
|
report_sample(0, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void report_sample(int64_t completed, int64_t total) const {
|
||||||
|
report("sampling", sample_detail_, completed, total, "steps");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sd
|
||||||
|
|
||||||
|
#endif // __SD_RUNTIME_GENERATION_PROGRESS_H__
|
||||||
@@ -698,6 +698,16 @@ struct sd_ctx_t {
|
|||||||
StableDiffusionGGML* sd = nullptr;
|
StableDiffusionGGML* sd = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void sd_set_generation_progress_callback(sd_ctx_t* ctx, sd_generation_progress_cb_t cb, void* data) {
|
||||||
|
if (ctx == nullptr || ctx->sd == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(ctx->sd->execution_mutex);
|
||||||
|
if (!ctx->sd->executing_) {
|
||||||
|
ctx->sd->generation_progress.set_callback(cb, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sd_ctx_t* new_sd_ctx(const sd_ctx_params_t* sd_ctx_params) {
|
sd_ctx_t* new_sd_ctx(const sd_ctx_params_t* sd_ctx_params) {
|
||||||
sd_ctx_t* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t));
|
sd_ctx_t* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t));
|
||||||
if (sd_ctx == nullptr) {
|
if (sd_ctx == nullptr) {
|
||||||
|
|||||||
Reference in new issue
Block a user