Compare commits

..

5 Commits

17 changed files with 99 additions and 41 deletions

View File

@ -156,8 +156,11 @@ the runner's graph-cut capacity checks.
Runtime capacity checks also leave 512 MiB of currently free device memory for Runtime capacity checks also leave 512 MiB of currently free device memory for
backend scratch buffers and pipelines, including with explicit backend assignments. backend scratch buffers and pipelines, including with explicit backend assignments.
They cap stale free-memory reports by the device's total memory minus tracked They cap free-memory reports by the device's total memory minus tracked
resident allocations and reject reports that exceed the device's total memory. resident allocations. Vulkan reports exceeding total memory are rejected because
its heap-budget subtraction can underflow. Other backends use the cap instead of
treating such reports as zero free memory. Failed checks log the reported free and
total memory alongside tracked weight and runtime allocations.
Components are considered in `diffusion`, `te`, `vae` order so that repeatedly Components are considered in `diffusion`, `te`, `vae` order so that repeatedly
used diffusion weights have priority. Each component's weights use the first used diffusion weights have priority. Each component's weights use the first

View File

@ -1,5 +1,14 @@
# Troubleshooting # Troubleshooting
## Video model used in image generation mode
If generation reports that a model cannot be run with `generate_image()`, add
`--mode vid_gen` to the CLI command. `--video-frames` alone does not select video
mode. Video models require this mode even when generating a single frame.
Library callers must use `generate_video()` for these models; use
`sd_ctx_supports_image_generation()` and `sd_ctx_supports_video_generation()` to
check the available generation modes.
## Completely black or white images or videos / NaNs ## Completely black or white images or videos / NaNs
Some ggml backends can encounter numerical overflow during inference, producing Some ggml backends can encounter numerical overflow during inference, producing

View File

@ -1,5 +1,7 @@
# How to Use # How to Use
Wan models require `-M vid_gen`, including single-frame generation. `--video-frames` alone does not select video mode. Library callers must use `generate_video()` instead of `generate_image()`.
## Download weights ## Download weights
- Download Wan - Download Wan

View File

@ -1754,7 +1754,7 @@ ArgOptions SDGenerationParams::get_options() {
on_scm_policy_arg}, on_scm_policy_arg},
{"", {"",
"--vae-tile-size", "--vae-tile-size",
"tile size for vae tiling, format [X]x[Y] (default: 32x32)", "tile size for vae tiling in latent units, not image pixels, format [X]x[Y] (default: 32x32)",
on_tile_size_arg}, on_tile_size_arg},
{"", {"",
"--vae-relative-tile-size", "--vae-relative-tile-size",
@ -2223,7 +2223,12 @@ bool SDGenerationParams::from_json_str(
LOG_ERROR("invalid end_image"); LOG_ERROR("invalid end_image");
return false; return false;
} }
if (!parse_image_array_json_field(j, "ref_images", 3, width, height, ref_images)) { if (!parse_image_array_json_field(j,
"ref_images",
3,
auto_resize_ref_image ? width : 0,
auto_resize_ref_image ? height : 0,
ref_images)) {
LOG_ERROR("invalid ref_images"); LOG_ERROR("invalid ref_images");
return false; return false;
} }

View File

@ -244,8 +244,12 @@ static bool build_sdapi_img_gen_request(const json& j,
SDImageOwner image_owner; SDImageOwner image_owner;
if (decode_base64_image(extra_image.get<std::string>(), if (decode_base64_image(extra_image.get<std::string>(),
3, 3,
request.gen_params.width_and_height_are_set() ? request.gen_params.width : 0, request.gen_params.auto_resize_ref_image && request.gen_params.width_and_height_are_set()
request.gen_params.width_and_height_are_set() ? request.gen_params.height : 0, ? request.gen_params.width
: 0,
request.gen_params.auto_resize_ref_image && request.gen_params.width_and_height_are_set()
? request.gen_params.height
: 0,
image_owner)) { image_owner)) {
const sd_image_t& image = image_owner.get(); const sd_image_t& image = image_owner.get();
request.gen_params.set_width_and_height_if_unset(image.width, image.height); request.gen_params.set_width_and_height_if_unset(image.width, image.height);

2
ggml

@ -1 +1 @@
Subproject commit f583f393cd5dfdc129360bbf75cb3d49ccc837a8 Subproject commit 4bf5f6000653b7881d00963cd6ddb665ccd62a8d

View File

@ -2219,7 +2219,10 @@ struct LLMEmbedder : public Conditioner {
false, false,
deepstack_image_embeds, deepstack_image_embeds,
image_grids); image_grids);
GGML_ASSERT(!hidden_states.empty()); if (hidden_states.empty()) {
LOG_ERROR("LLM prompt encoding failed");
return {};
}
hidden_states = apply_token_weights(std::move(hidden_states), weights); hidden_states = apply_token_weights(std::move(hidden_states), weights);
GGML_ASSERT(hidden_states.shape()[1] > prompt_template_encode_start_idx); GGML_ASSERT(hidden_states.shape()[1] > prompt_template_encode_start_idx);

View File

@ -478,7 +478,11 @@ namespace sd::backend_fit {
return true; return true;
} }
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prefer_temporal_tiling) { bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prefer_temporal_tiling, ggml_status status) {
// Execution failures can leave the device unusable; tiling only helps with allocation failures.
if (status != GGML_STATUS_ALLOC_FAILED) {
return false;
}
const char* retry_mode = nullptr; const char* retry_mode = nullptr;
if (prefer_temporal_tiling && !tiling_params.temporal_tiling) { if (prefer_temporal_tiling && !tiling_params.temporal_tiling) {
tiling_params.temporal_tiling = true; tiling_params.temporal_tiling = true;
@ -498,7 +502,7 @@ namespace sd::backend_fit {
return false; return false;
} }
LOG_WARN("VAE decode failed (likely out of memory); retrying with %s tiling", LOG_WARN("VAE decode ran out of memory; retrying with %s tiling",
retry_mode); retry_mode);
return true; return true;
} }

View File

@ -16,7 +16,8 @@ namespace sd::backend_fit {
std::string& params_spec); std::string& params_spec);
bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params, bool prepare_vae_decode_retry_tiling(sd_tiling_params_t& tiling_params,
bool prefer_temporal_tiling); bool prefer_temporal_tiling,
ggml_status status);
} // namespace sd::backend_fit } // namespace sd::backend_fit

View File

@ -590,6 +590,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
bool auto_runner_end, bool auto_runner_end,
bool no_return, bool no_return,
const std::function<bool()>& read_outputs) { const std::function<bool()>& read_outputs) {
last_compute_status_ = GGML_STATUS_FAILED;
if (graph_active_) { if (graph_active_) {
LOG_ERROR("%s does not support reentrant graph execution", get_desc().c_str()); LOG_ERROR("%s does not support reentrant graph execution", get_desc().c_str());
return std::nullopt; return std::nullopt;
@ -613,7 +614,9 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
GGMLRunner& runner; GGMLRunner& runner;
const bool& success; const bool& success;
~GraphEndGuard() { ~GraphEndGuard() {
runner.workspace_.segment_end(); if (!runner.workspace_.segment_end()) {
runner.last_compute_status_ = GGML_STATUS_FAILED;
}
runner.cache_.graph_end(false); runner.cache_.graph_end(false);
runner.cut_cache_.clear(); runner.cut_cache_.clear();
runner.free_compute_ctx(); runner.free_compute_ctx();
@ -642,6 +645,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
try { try {
output = execute_graph(graph, n_threads, no_return, read_outputs); output = execute_graph(graph, n_threads, no_return, read_outputs);
} catch (const std::exception& error) { } catch (const std::exception& error) {
last_compute_status_ = GGML_STATUS_FAILED;
LOG_ERROR("%s graph execution failed on %s: %s", get_desc().c_str(), LOG_ERROR("%s graph execution failed on %s: %s", get_desc().c_str(),
ggml_backend_name(runtime_backend), error.what()); ggml_backend_name(runtime_backend), error.what());
return std::nullopt; return std::nullopt;
@ -649,6 +653,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
success = output.has_value(); success = output.has_value();
if (success) { if (success) {
cache_.graph_end(true); cache_.graph_end(true);
last_compute_status_ = GGML_STATUS_SUCCESS;
} }
return output; return output;
} }
@ -766,6 +771,7 @@ bool GGMLRunner::execute_segment(ggml_cgraph* graph, int n_threads) {
} }
workspace_.synchronize(); workspace_.synchronize();
if (status != GGML_STATUS_SUCCESS) { if (status != GGML_STATUS_SUCCESS) {
last_compute_status_ = status;
LOG_ERROR("%s compute failed: %s", get_desc().c_str(), ggml_status_to_string(status)); LOG_ERROR("%s compute failed: %s", get_desc().c_str(), ggml_status_to_string(status));
return false; return false;
} }
@ -818,6 +824,7 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
const auto& cached_plan = resolve_graph_cut_plan(graph); const auto& cached_plan = resolve_graph_cut_plan(graph);
const auto full_measurement = measure(graph, cached_plan.compute_buffer_size); const auto full_measurement = measure(graph, cached_plan.compute_buffer_size);
if (full_measurement.buffers.empty()) { if (full_measurement.buffers.empty()) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return std::nullopt; return std::nullopt;
} }
auto manager = residency_manager.lock(); auto manager = residency_manager.lock();
@ -888,7 +895,9 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
SegmentGraphBindings& bindings; SegmentGraphBindings& bindings;
ggml_context* context; ggml_context* context;
~SegmentCleanup() { ~SegmentCleanup() {
runner.workspace_.segment_end(); if (!runner.workspace_.segment_end()) {
runner.last_compute_status_ = GGML_STATUS_FAILED;
}
bindings.restore(); bindings.restore();
weights.segment_end(); weights.segment_end();
ggml_free(context); ggml_free(context);
@ -898,6 +907,7 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
auto measurement = segmented ? measure(segment_graph, segment.compute_buffer_size) : full_measurement; auto measurement = segmented ? measure(segment_graph, segment.compute_buffer_size) : full_measurement;
if (!workspace_.prepare(measurement)) { if (!workspace_.prepare(measurement)) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return fail_segment("workspace preparation"); return fail_segment("workspace preparation");
} }
const size_t cut_bytes = last ? 0 : cut_cache_.estimate_output_bytes(graph, segment); const size_t cut_bytes = last ? 0 : cut_cache_.estimate_output_bytes(graph, segment);
@ -912,7 +922,11 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
sync_runtime_residency(); sync_runtime_residency();
requests = memory_requests(measurement.buffers, new_cache_bytes); requests = memory_requests(measurement.buffers, new_cache_bytes);
} }
return weights.ensure_segment_capacity(index, requests); const bool ready = weights.ensure_segment_capacity(index, requests);
if (!ready && manager != nullptr) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
}
return ready;
}; };
if (!weights.segment_start(index, ensure_capacity)) { if (!weights.segment_start(index, ensure_capacity)) {
return fail_segment("weight preparation"); return fail_segment("weight preparation");
@ -921,12 +935,17 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
if (!workspace_.measurement_matches(segment_graph, measurement)) { if (!workspace_.measurement_matches(segment_graph, measurement)) {
measurement = measure(segment_graph, segment.compute_buffer_size); measurement = measure(segment_graph, segment.compute_buffer_size);
} }
if (!workspace_.prepare(measurement) || !ensure_capacity()) { if (!workspace_.prepare(measurement)) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return fail_segment("workspace preparation");
}
if (!ensure_capacity()) {
return fail_segment("workspace capacity check"); return fail_segment("workspace capacity check");
} }
if (!workspace_.allocate(segment_graph, [&](ggml_backend_sched_t scheduler, ggml_cgraph* current) { if (!workspace_.allocate(segment_graph, [&](ggml_backend_sched_t scheduler, ggml_cgraph* current) {
pin_multi_device_nodes(scheduler, current); pin_multi_device_nodes(scheduler, current);
})) { })) {
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
return fail_segment("workspace allocation"); return fail_segment("workspace allocation");
} }
for (const auto& size : measurement.buffers) { for (const auto& size : measurement.buffers) {
@ -964,6 +983,7 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
} }
} }
if (!workspace_.segment_end()) { if (!workspace_.segment_end()) {
last_compute_status_ = GGML_STATUS_FAILED;
return fail_segment("workspace synchronization"); return fail_segment("workspace synchronization");
} }
// Final outputs and their callbacks may still be views of consumed cuts. // Final outputs and their callbacks may still be views of consumed cuts.

View File

@ -130,7 +130,8 @@ ggml_tensor* ggml_ext_attention_ext(GGMLRunnerContext* ctx,
struct GGMLRunner { struct GGMLRunner {
private: private:
std::map<ggml_backend_t, size_t> logged_compute_bytes_; std::map<ggml_backend_t, size_t> logged_compute_bytes_;
size_t logged_segment_count_ = 0; size_t logged_segment_count_ = 0;
ggml_status last_compute_status_ = GGML_STATUS_SUCCESS;
sd::ComputeWorkspace::Measurement measure(ggml_cgraph* graph, size_t direct_bytes); sd::ComputeWorkspace::Measurement measure(ggml_cgraph* graph, size_t direct_bytes);
std::vector<DeviceMemoryRequest> memory_requests(const std::vector<sd::BackendBufferSize>& sizes, std::vector<DeviceMemoryRequest> memory_requests(const std::vector<sd::BackendBufferSize>& sizes,
@ -335,6 +336,8 @@ public:
bool no_return = false, bool no_return = false,
const std::function<bool()>& read_outputs = {}); const std::function<bool()>& read_outputs = {});
ggml_status last_compute_status() const { return last_compute_status_; }
void set_flash_attention_enabled(bool enabled) { void set_flash_attention_enabled(bool enabled) {
flash_attn_enabled = enabled; flash_attn_enabled = enabled;
} }

View File

@ -252,6 +252,14 @@ static inline bool sd_version_is_sensenova_u1(SDVersion version) {
return version == VERSION_SENSENOVA_U1_5; return version == VERSION_SENSENOVA_U1_5;
} }
static inline bool sd_version_supports_video_generation(SDVersion version) {
return version == VERSION_SVD || sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_lingbot_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version);
}
static inline bool sd_version_supports_image_generation(SDVersion version) {
return !sd_version_supports_video_generation(version);
}
static inline bool sd_version_uses_flux_vae(SDVersion version) { static inline bool sd_version_uses_flux_vae(SDVersion version) {
if (sd_version_is_flux(version) || sd_version_is_z_image(version) || sd_version_is_boogu_image(version) || sd_version_is_longcat(version)) { if (sd_version_is_flux(version) || sd_version_is_z_image(version) || sd_version_is_boogu_image(version) || sd_version_is_longcat(version)) {
return true; return true;

View File

@ -1613,7 +1613,8 @@ void ModelManager::remove_runtime_owner(uintptr_t owner_id) {
ModelManager::CapacityCheck ModelManager::check_capacity( ModelManager::CapacityCheck ModelManager::check_capacity(
const DeviceMemoryRequest& request, const DeviceMemoryRequest& request,
const std::vector<TensorState*>& states) const { const std::vector<TensorState*>& states,
bool log_details) const {
CapacityCheck result; CapacityCheck result;
if (request.compute_backend == nullptr || sd_backend_is_cpu(request.compute_backend)) { if (request.compute_backend == nullptr || sd_backend_is_cpu(request.compute_backend)) {
return result; return result;
@ -1631,16 +1632,23 @@ ModelManager::CapacityCheck ModelManager::check_capacity(
} }
size_t free_bytes = 0, total_bytes = 0; size_t free_bytes = 0, total_bytes = 0;
ggml_backend_dev_memory(device, &free_bytes, &total_bytes); ggml_backend_dev_memory(device, &free_bytes, &total_bytes);
const size_t weights_resident = compute_backend_resident_bytes(backend);
const size_t other_runtime = other_runtime_resident_bytes(request.owner_id, backend);
const size_t resident = add(weights_resident, add(other_runtime, request.runtime_resident_bytes));
if (log_details) {
LOG_WARN("model manager memory on %s: reported free %.2f MB / total %.2f MB, tracked weights %.2f MB / other runtime %.2f MB / current runtime %.2f MB",
ggml_backend_name(backend),
free_bytes / (1024.0 * 1024.0), total_bytes / (1024.0 * 1024.0),
weights_resident / (1024.0 * 1024.0), other_runtime / (1024.0 * 1024.0),
request.runtime_resident_bytes / (1024.0 * 1024.0));
}
if (free_bytes == 0 && total_bytes == 0) { if (free_bytes == 0 && total_bytes == 0) {
return SIZE_MAX; return SIZE_MAX;
} }
// Vulkan's heap budget subtraction can underflow when usage exceeds the budget. // Vulkan's heap budget subtraction can underflow when usage exceeds the budget.
if (total_bytes > 0 && free_bytes > total_bytes) { if (total_bytes > 0 && free_bytes > total_bytes && sd_backend_is(backend, "Vulkan")) {
return size_t{0}; return size_t{0};
} }
const size_t resident = add(compute_backend_resident_bytes(backend),
add(other_runtime_resident_bytes(request.owner_id, backend),
request.runtime_resident_bytes));
if (total_bytes > 0) { if (total_bytes > 0) {
free_bytes = std::min(free_bytes, resident < total_bytes ? total_bytes - resident : 0); free_bytes = std::min(free_bytes, resident < total_bytes ? total_bytes - resident : 0);
} }
@ -1786,7 +1794,7 @@ bool ModelManager::ensure_compute_backend_capacity(
} }
} }
const auto capacity = check_capacity(request, required_states); const auto capacity = check_capacity(request, required_states, true);
const std::string available_device = capacity.available_device_bytes == SIZE_MAX const std::string available_device = capacity.available_device_bytes == SIZE_MAX
? "unknown" ? "unknown"
: sd_format("%.2f MB", capacity.available_device_bytes / (1024.0 * 1024.0)); : sd_format("%.2f MB", capacity.available_device_bytes / (1024.0 * 1024.0));

View File

@ -157,7 +157,8 @@ private:
} }
}; };
CapacityCheck check_capacity(const DeviceMemoryRequest& request, CapacityCheck check_capacity(const DeviceMemoryRequest& request,
const std::vector<TensorState*>& states) const; const std::vector<TensorState*>& states,
bool log_details = false) const;
ggml_backend_buffer_type_t params_buffer_type_for(const TensorState& state) const; ggml_backend_buffer_type_t params_buffer_type_for(const TensorState& state) const;
ggml_backend_buffer_type_t split_buffer_type_for(const TensorState& state) const; ggml_backend_buffer_type_t split_buffer_type_for(const TensorState& state) const;

View File

@ -2766,7 +2766,8 @@ sd::Tensor<float> StableDiffusionGGML::decode_first_stage(const sd::Tensor<float
auto decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y); auto decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y);
const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode(); const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode();
while (decoded.empty() && while (decoded.empty() &&
sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling)) { sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling,
first_stage_model->last_compute_status())) {
decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y); decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y);
} }
return decoded; return decoded;

View File

@ -789,15 +789,9 @@ namespace sd::pipeline {
return false; return false;
} }
// MiniMax-H3 is video-only. Its denoiser always splits the packed latent into a video and an if (!sd_version_supports_image_generation(sd->version)) {
// audio half, and only generate_video ever computes the audio length, so reaching this LOG_ERROR("%s cannot be run with generate_image(); use generate_video() or --mode vid_gen in the CLI",
// function with an H3 checkpoint is guaranteed to die on model_version_to_str[sd->version]);
// GGML_ASSERT(!audio_input_cache.empty()) with a core dump, after the several minutes it
// takes to load the weights, and with nothing in the output pointing at the missing --mode.
// (The AnimateDiff path below routes vid_gen back through here, but that is SD1.5 plus a
// motion module, never H3.)
if (sd_version_is_minimax_h3(sd->version)) {
LOG_ERROR("MiniMax-H3 is a video model and cannot be run in img_gen mode; use --mode vid_gen");
return false; return false;
} }

View File

@ -630,14 +630,6 @@ struct sd_ctx_t {
StableDiffusionGGML* sd = nullptr; StableDiffusionGGML* sd = nullptr;
}; };
static bool sd_version_supports_video_generation(SDVersion version) {
return version == VERSION_SVD || sd_version_is_wan(version) || sd_version_is_hunyuan_video(version) || sd_version_is_lingbot_video(version) || sd_version_is_ltxav(version) || sd_version_is_minimax_h3(version);
}
static bool sd_version_supports_image_generation(SDVersion version) {
return !sd_version_supports_video_generation(version);
}
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) {