mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-04 11:18:33 +08:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e01206574b | ||
|
|
6dcb5bbd42 | ||
|
|
97d932b8f8 | ||
|
|
78557f88d9 | ||
|
|
2726dd35c2 | ||
|
|
74988b290e |
+5
-2
@@ -156,8 +156,11 @@ the runner's graph-cut capacity checks.
|
||||
|
||||
Runtime capacity checks also leave 512 MiB of currently free device memory for
|
||||
backend scratch buffers and pipelines, including with explicit backend assignments.
|
||||
They cap stale free-memory reports by the device's total memory minus tracked
|
||||
resident allocations and reject reports that exceed the device's total memory.
|
||||
They cap free-memory reports by the device's total memory minus tracked
|
||||
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
|
||||
used diffusion weights have priority. Each component's weights use the first
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
# Sol-Attn
|
||||
|
||||
`--sol-attn` enables native CUDA Sol-Attn in the diffusion model, including the
|
||||
high-noise diffusion model when present. It uses the shared attention dispatcher
|
||||
without classifying tokens as text, images, or video. Python, PyTorch, Triton,
|
||||
and CuTe DSL are not needed to build or run it.
|
||||
|
||||
This implementation follows the diagonal-threshold algorithm in
|
||||
[NVlabs/Sana's Sol-Attn](https://github.com/NVlabs/Sana/tree/sol-engine/techniques/sparse_backends/sol_attn).
|
||||
It summarizes 64-token KV blocks, selects exact blocks using proxy scores and
|
||||
an online threshold, and approximates the remaining blocks using their K means
|
||||
and V sums. Adjacent blocks remain exact. Both contributions share an online
|
||||
softmax normalizer. Q/K/V and probability tiles use BF16 Tensor Cores with FP32
|
||||
accumulation; the BF16 result is returned through the existing FP32 interface.
|
||||
|
||||
## Build
|
||||
|
||||
Use patched GGML, CUDA Toolkit 12.0 or newer, and an NVIDIA GPU with compute
|
||||
capability 8.0 or newer. Compile kernels for the target GPU:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -DSD_CUDA=ON -DSD_USE_UPSTREAM_GGML=OFF
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
The feature is compiled with the CUDA backend; no separate build option is
|
||||
required. Upstream GGML and non-CUDA backends do not support it. A system GGML
|
||||
must provide the matching patched API and CUDA implementation. Tensor-parallel
|
||||
row splitting is not supported; layer splitting requires supported devices.
|
||||
|
||||
## Use
|
||||
|
||||
Add `--sol-attn` to an existing generation command:
|
||||
|
||||
```sh
|
||||
sd-cli ... --sol-attn
|
||||
sd-cli ... --sol-attn --sol-attn-tau 1.0
|
||||
```
|
||||
|
||||
The default threshold coefficient is `1.0`. Larger coefficients select fewer
|
||||
blocks for exact attention. The coefficient must be finite; zero does not mean
|
||||
dense attention. Omit `--sol-attn` to disable the feature.
|
||||
|
||||
The native kernel supports unmasked, noncausal attention with head dimension
|
||||
128, equal Q/K/V sequence lengths and head counts, and multiple batches. Other
|
||||
attention operations fall back to FlashAttention when available, then ordinary
|
||||
attention. Existing attention scaling overrides remain effective. `--fa` and
|
||||
`--diffusion-fa` may be used together with Sol-Attn; `--sage-attn` is mutually
|
||||
exclusive. Text encoders and VAEs retain their existing attention selection.
|
||||
|
||||
Initialization reports an error if the requested diffusion backend cannot run
|
||||
Sol-Attn. Graph logs report the number of Sol-Attn and FlashAttention nodes and
|
||||
warn when no Sol-Attn nodes are selected. CUDA execution errors are not silently
|
||||
converted into dense attention.
|
||||
|
||||
This is approximate attention. Validate quality and end-to-end speed with the
|
||||
same prompt, seed, dimensions, frame count, and sampling settings. Include
|
||||
packing, preprocessing, offload, and decode time in comparisons. Short sequences
|
||||
may not benefit. Upstream combined pipeline speedups are not measurements of
|
||||
this native kernel. Exact-covariance thresholds, text sinks, Morton ordering,
|
||||
and step/layer schedules are not implemented.
|
||||
|
||||
## Validation
|
||||
|
||||
On an RTX 4090 with CUDA 12.4, Wan 2.1 T2V 1.3B was tested at 832x480,
|
||||
33 frames, 20 Euler steps, seed 42, CFG 6, and flow shift 3, using the prompt
|
||||
`a lovely cat` and the same negative prompt for every run:
|
||||
|
||||
| Attention | Sampling time | Total process time |
|
||||
| --- | ---: | ---: |
|
||||
| FlashAttention | 45.73 s | 74.63 s |
|
||||
| Sol-Attn, tau 1 | 37.17 s | 66.20 s |
|
||||
| Sol-Attn, tau 0 | 40.66 s | 68.50 s |
|
||||
|
||||
These are single-run measurements. The graph selected 30 Sol-Attn nodes and
|
||||
30 FlashAttention nodes. At tau 1, sampled video frames showed washed-out
|
||||
colors and reduced detail. Tau 0 improved clarity in this example, but still
|
||||
changed the composition. Neither setting guarantees the baseline's quality.
|
||||
For this Wan command, `--sol-attn --sol-attn-tau 0` is a more conservative
|
||||
starting point. In the one-frame case, tau 1 increased warm sampling time from
|
||||
0.140 to 0.148 seconds per step.
|
||||
|
||||
Validation also covered 15 numerical reference cases, 11 layout/scaling/fallback
|
||||
cases, CUDA memory checking, and 36 existing SageAttention regression cases.
|
||||
CLI and server CUDA builds and the upstream GGML CPU library build passed.
|
||||
Other GPU architectures, multi-GPU execution, and other models have not been
|
||||
tested.
|
||||
|
||||
## Library API
|
||||
|
||||
Configure Sol-Attn in `sd_ctx_params_t` before creating the context:
|
||||
|
||||
```cpp
|
||||
sd_ctx_params_t params;
|
||||
sd_ctx_params_init(¶ms);
|
||||
// Set model paths and other context options here.
|
||||
params.sol_attn = true;
|
||||
params.sol_attn_tau = 1.0f;
|
||||
sd_ctx_t* ctx = new_sd_ctx(¶ms);
|
||||
```
|
||||
|
||||
`sd_ctx_params_init` defaults `sol_attn` to false and `sol_attn_tau` to 1.0.
|
||||
`new_sd_ctx` returns null for a nonfinite threshold, unavailable requested
|
||||
backends, or a conflict with SageAttention. The context owns a copy of these
|
||||
settings; changing the input structure after creation does not reconfigure it.
|
||||
Applications must be rebuilt against the updated `sd_ctx_params_t` definition.
|
||||
@@ -1,5 +1,14 @@
|
||||
# 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
|
||||
|
||||
Some ggml backends can encounter numerical overflow during inference, producing
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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 Wan
|
||||
|
||||
@@ -25,7 +25,3 @@ Metadata mode inspects PNG/JPEG container metadata without loading any model:
|
||||
|
||||
For completely black or white images or videos, NaNs, and the `--linear-scale` /
|
||||
`--attn-scale` workaround, see [Troubleshooting](../../docs/troubleshooting.md).
|
||||
|
||||
For native CUDA sparse attention in the diffusion model, use `--sol-attn`.
|
||||
See [Sol-Attn](../../docs/sol_attention.md) for requirements, supported shapes,
|
||||
and the `--sol-attn-tau` threshold coefficient.
|
||||
|
||||
@@ -622,10 +622,6 @@ ArgOptions SDContextParams::get_options() {
|
||||
"--sage-attn",
|
||||
"use native CUDA SageAttention in the diffusion model, with flash/default attention fallback",
|
||||
true, &sage_attn},
|
||||
{"",
|
||||
"--sol-attn",
|
||||
"use native CUDA Sol-Attn in the diffusion model, with flash/default attention fallback",
|
||||
true, &sol_attn},
|
||||
{"",
|
||||
"--diffusion-conv-direct",
|
||||
"use ggml_conv2d_direct in the diffusion model",
|
||||
@@ -723,8 +719,6 @@ ArgOptions SDContextParams::get_options() {
|
||||
return 1;
|
||||
};
|
||||
|
||||
options.float_options.push_back({"", "--sol-attn-tau", "Sol-Attn routing threshold coefficient (default: 1; higher selects fewer exact blocks)", &sol_attn_tau});
|
||||
|
||||
options.manual_options = {
|
||||
{"",
|
||||
"--linear-scale",
|
||||
@@ -828,14 +822,6 @@ bool SDContextParams::resolve(SDMode mode) {
|
||||
}
|
||||
|
||||
bool SDContextParams::validate(SDMode mode) {
|
||||
if (sol_attn && sage_attn) {
|
||||
LOG_ERROR("--sol-attn and --sage-attn cannot be enabled together");
|
||||
return false;
|
||||
}
|
||||
if (!std::isfinite(sol_attn_tau)) {
|
||||
LOG_ERROR("--sol-attn-tau must be finite");
|
||||
return false;
|
||||
}
|
||||
if (mode == CONVERT) {
|
||||
const bool has_convert_input = model_path.length() != 0 ||
|
||||
clip_l_path.length() != 0 ||
|
||||
@@ -957,8 +943,6 @@ std::string SDContextParams::to_string() const {
|
||||
<< " flash_attn: " << (flash_attn ? "true" : "false") << ",\n"
|
||||
<< " diffusion_flash_attn: " << (diffusion_flash_attn ? "true" : "false") << ",\n"
|
||||
<< " sage_attn: " << (sage_attn ? "true" : "false") << ",\n"
|
||||
<< " sol_attn: " << (sol_attn ? "true" : "false") << ",\n"
|
||||
<< " sol_attn_tau: " << sol_attn_tau << ",\n"
|
||||
<< " linear_scale: " << linear_scale << ",\n"
|
||||
<< " attn_scale: " << attn_scale << ",\n"
|
||||
<< " diffusion_conv_direct: " << (diffusion_conv_direct ? "true" : "false") << ",\n"
|
||||
@@ -1017,8 +1001,6 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
||||
sd_ctx_params.flash_attn = flash_attn;
|
||||
sd_ctx_params.diffusion_flash_attn = diffusion_flash_attn;
|
||||
sd_ctx_params.sage_attn = sage_attn;
|
||||
sd_ctx_params.sol_attn = sol_attn;
|
||||
sd_ctx_params.sol_attn_tau = sol_attn_tau;
|
||||
sd_ctx_params.linear_scale = linear_scale;
|
||||
sd_ctx_params.attn_scale = attn_scale;
|
||||
sd_ctx_params.tae_preview_only = taesd_preview;
|
||||
@@ -1772,7 +1754,7 @@ ArgOptions SDGenerationParams::get_options() {
|
||||
on_scm_policy_arg},
|
||||
{"",
|
||||
"--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},
|
||||
{"",
|
||||
"--vae-relative-tile-size",
|
||||
@@ -2241,7 +2223,12 @@ bool SDGenerationParams::from_json_str(
|
||||
LOG_ERROR("invalid end_image");
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,8 +171,6 @@ struct SDContextParams {
|
||||
bool flash_attn = false;
|
||||
bool diffusion_flash_attn = false;
|
||||
bool sage_attn = false;
|
||||
bool sol_attn = false;
|
||||
float sol_attn_tau = 1.f;
|
||||
bool diffusion_conv_direct = false;
|
||||
bool vae_conv_direct = false;
|
||||
|
||||
|
||||
@@ -164,18 +164,36 @@ static bool build_openai_edit_request(const httplib::Request& req,
|
||||
reinterpret_cast<const char*>(bytes.data()),
|
||||
static_cast<int>(bytes.size()),
|
||||
img_w, img_h,
|
||||
width, height, 3);
|
||||
0, 0, 3);
|
||||
if (raw_pixels == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool is_first_ref_image = request.gen_params.ref_images.empty();
|
||||
SDImageOwner image_owner({(uint32_t)img_w, (uint32_t)img_h, 3, raw_pixels});
|
||||
request.gen_params.set_width_and_height_if_unset(image_owner.get().width, image_owner.get().height);
|
||||
request.gen_params.ref_images.push_back(std::move(image_owner));
|
||||
}
|
||||
|
||||
if (!request.gen_params.ref_images.empty()) {
|
||||
request.gen_params.init_image = request.gen_params.ref_images.front();
|
||||
if (is_first_ref_image) {
|
||||
int init_w = 0;
|
||||
int init_h = 0;
|
||||
if (request.gen_params.width_and_height_are_set()) {
|
||||
init_w = request.gen_params.width;
|
||||
init_h = request.gen_params.height;
|
||||
}
|
||||
|
||||
int init_img_w = 0;
|
||||
int init_img_h = 0;
|
||||
uint8_t* init_pixels = load_image_from_memory(
|
||||
reinterpret_cast<const char*>(bytes.data()),
|
||||
static_cast<int>(bytes.size()),
|
||||
init_img_w, init_img_h,
|
||||
init_w, init_h, 3);
|
||||
if (init_pixels != nullptr) {
|
||||
request.gen_params.init_image.reset({(uint32_t)init_img_w, (uint32_t)init_img_h, 3, init_pixels});
|
||||
}
|
||||
}
|
||||
|
||||
request.gen_params.ref_images.push_back(std::move(image_owner));
|
||||
}
|
||||
|
||||
if (!mask_bytes.empty()) {
|
||||
|
||||
@@ -244,8 +244,12 @@ static bool build_sdapi_img_gen_request(const json& j,
|
||||
SDImageOwner image_owner;
|
||||
if (decode_base64_image(extra_image.get<std::string>(),
|
||||
3,
|
||||
request.gen_params.width_and_height_are_set() ? request.gen_params.width : 0,
|
||||
request.gen_params.width_and_height_are_set() ? request.gen_params.height : 0,
|
||||
request.gen_params.auto_resize_ref_image && 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.height
|
||||
: 0,
|
||||
image_owner)) {
|
||||
const sd_image_t& image = image_owner.get();
|
||||
request.gen_params.set_width_and_height_if_unset(image.width, image.height);
|
||||
|
||||
+1
-1
Submodule ggml updated: 223feb34ab...4bf5f60006
@@ -247,8 +247,6 @@ typedef struct {
|
||||
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
|
||||
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
|
||||
bool sage_attn;
|
||||
bool sol_attn;
|
||||
float sol_attn_tau;
|
||||
} sd_ctx_params_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -2219,7 +2219,10 @@ struct LLMEmbedder : public Conditioner {
|
||||
false,
|
||||
deepstack_image_embeds,
|
||||
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);
|
||||
GGML_ASSERT(hidden_states.shape()[1] > prompt_template_encode_start_idx);
|
||||
|
||||
|
||||
@@ -478,7 +478,11 @@ namespace sd::backend_fit {
|
||||
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;
|
||||
if (prefer_temporal_tiling && !tiling_params.temporal_tiling) {
|
||||
tiling_params.temporal_tiling = true;
|
||||
@@ -498,7 +502,7 @@ namespace sd::backend_fit {
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ namespace sd::backend_fit {
|
||||
std::string& params_spec);
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -623,9 +623,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
bool skip_reshape,
|
||||
bool flash_attn,
|
||||
float kv_scale,
|
||||
bool sage_attn,
|
||||
bool sol_attn,
|
||||
float sol_attn_tau) { // avoid overflow
|
||||
bool sage_attn) { // avoid overflow
|
||||
int64_t L_q;
|
||||
int64_t L_k;
|
||||
int64_t C;
|
||||
@@ -717,23 +715,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
};
|
||||
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
if (sol_attn && mask == nullptr && d_head == 128 && L_q == L_k && n_head == n_kv_head) {
|
||||
auto q_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, q->type == GGML_TYPE_F32 ? q : ggml_cast(ctx, q, GGML_TYPE_F32)), d_head, L_q, n_head, N);
|
||||
auto k_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, k->type == GGML_TYPE_F32 ? k : ggml_cast(ctx, k, GGML_TYPE_F32)), d_head, L_k, n_kv_head, N);
|
||||
auto v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v, 0, 2, 1, 3));
|
||||
if (v_in->type != GGML_TYPE_F32) {
|
||||
v_in = ggml_cast(ctx, v_in, GGML_TYPE_F32);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
|
||||
}
|
||||
auto out = ggml_sol_attn(ctx, q_in, k_in, v_in, scale / kv_scale, sol_attn_tau);
|
||||
if (ggml_backend_supports_op(backend, out)) {
|
||||
kqv = kv_scale != 1.0f ? ggml_ext_scale(ctx, out, 1.0f / kv_scale) : out;
|
||||
}
|
||||
}
|
||||
if (kqv == nullptr && sage_attn && mask == nullptr && d_head > 0 && d_head <= 128) {
|
||||
if (sage_attn && mask == nullptr && d_head > 0 && d_head <= 128) {
|
||||
auto q_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, q->type == GGML_TYPE_F32 ? q : ggml_cast(ctx, q, GGML_TYPE_F32)), d_head, L_q, n_head, N);
|
||||
auto k_in = ggml_reshape_4d(ctx, ggml_ext_cont(ctx, k->type == GGML_TYPE_F32 ? k : ggml_cast(ctx, k, GGML_TYPE_F32)), d_head, L_k, n_kv_head, N);
|
||||
auto v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v, 0, 2, 1, 3));
|
||||
@@ -762,7 +744,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (kqv == nullptr && (flash_attn || sage_attn || sol_attn)) {
|
||||
if (kqv == nullptr && (flash_attn || sage_attn)) {
|
||||
// LOG_VERBOSE("attention_ext L_q:%d L_k:%d n_head:%d C:%d d_head:%d N:%d", L_q, L_k, n_head, C, d_head, N);
|
||||
bool can_use_flash_attn = true;
|
||||
if (mask != nullptr) {
|
||||
|
||||
@@ -217,13 +217,11 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
ggml_tensor* k,
|
||||
ggml_tensor* v,
|
||||
int64_t n_head,
|
||||
ggml_tensor* mask = nullptr,
|
||||
bool skip_reshape = false,
|
||||
bool flash_attn = false,
|
||||
float kv_scale = 1.0f,
|
||||
bool sage_attn = false,
|
||||
bool sol_attn = false,
|
||||
float sol_attn_tau = 1.0f);
|
||||
ggml_tensor* mask = nullptr,
|
||||
bool skip_reshape = false,
|
||||
bool flash_attn = false,
|
||||
float kv_scale = 1.0f,
|
||||
bool sage_attn = false);
|
||||
|
||||
ggml_tensor* ggml_ext_layer_norm(ggml_context* ctx,
|
||||
ggml_tensor* x,
|
||||
|
||||
+25
-23
@@ -25,7 +25,7 @@ ggml_tensor* ggml_ext_attention_ext(GGMLRunnerContext* ctx,
|
||||
if (ctx->attn_scale > 0.f) {
|
||||
kv_scale = ctx->attn_scale;
|
||||
}
|
||||
return ggml_ext_attention_ext(ctx->ggml_ctx, ctx->backend, q, k, v, n_head, mask, skip_reshape, flash_attn, kv_scale, ctx->sage_attn_enabled, ctx->sol_attn_enabled, ctx->sol_attn_tau);
|
||||
return ggml_ext_attention_ext(ctx->ggml_ctx, ctx->backend, q, k, v, n_head, mask, skip_reshape, flash_attn, kv_scale, ctx->sage_attn_enabled);
|
||||
}
|
||||
|
||||
void GGMLRunner::alloc_params_ctx() {
|
||||
@@ -164,22 +164,6 @@ ggml_cgraph* GGMLRunner::get_compute_graph(get_graph_cb_t get_graph) {
|
||||
}
|
||||
}
|
||||
prepare_build_in_tensor_after(gf);
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
if (sol_attn_enabled && !sol_attn_graph_logged) {
|
||||
int sol_nodes = 0;
|
||||
int flash_nodes = 0;
|
||||
for (int i = 0; i < ggml_graph_n_nodes(gf); ++i) {
|
||||
const auto op = ggml_graph_node(gf, i)->op;
|
||||
sol_nodes += op == GGML_OP_SOL_ATTN;
|
||||
flash_nodes += op == GGML_OP_FLASH_ATTN_EXT;
|
||||
}
|
||||
LOG_INFO("Sol-Attn graph: %d Sol-Attn nodes, %d FlashAttention nodes", sol_nodes, flash_nodes);
|
||||
if (sol_nodes == 0) {
|
||||
LOG_WARN("This graph has no attention operations supported by Sol-Attn");
|
||||
}
|
||||
sol_attn_graph_logged = true;
|
||||
}
|
||||
#endif
|
||||
return gf;
|
||||
}
|
||||
|
||||
@@ -537,8 +521,6 @@ GGMLRunnerContext GGMLRunner::get_context() {
|
||||
runner_ctx.backend = runtime_backend;
|
||||
runner_ctx.flash_attn_enabled = flash_attn_enabled;
|
||||
runner_ctx.sage_attn_enabled = sage_attn_enabled;
|
||||
runner_ctx.sol_attn_enabled = sol_attn_enabled;
|
||||
runner_ctx.sol_attn_tau = sol_attn_tau;
|
||||
runner_ctx.linear_scale = linear_scale;
|
||||
runner_ctx.attn_scale = attn_scale;
|
||||
runner_ctx.conv2d_direct_enabled = conv2d_direct_enabled;
|
||||
@@ -608,6 +590,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
|
||||
bool auto_runner_end,
|
||||
bool no_return,
|
||||
const std::function<bool()>& read_outputs) {
|
||||
last_compute_status_ = GGML_STATUS_FAILED;
|
||||
if (graph_active_) {
|
||||
LOG_ERROR("%s does not support reentrant graph execution", get_desc().c_str());
|
||||
return std::nullopt;
|
||||
@@ -631,7 +614,9 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
|
||||
GGMLRunner& runner;
|
||||
const bool& success;
|
||||
~GraphEndGuard() {
|
||||
runner.workspace_.segment_end();
|
||||
if (!runner.workspace_.segment_end()) {
|
||||
runner.last_compute_status_ = GGML_STATUS_FAILED;
|
||||
}
|
||||
runner.cache_.graph_end(false);
|
||||
runner.cut_cache_.clear();
|
||||
runner.free_compute_ctx();
|
||||
@@ -660,6 +645,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
|
||||
try {
|
||||
output = execute_graph(graph, n_threads, no_return, read_outputs);
|
||||
} catch (const std::exception& error) {
|
||||
last_compute_status_ = GGML_STATUS_FAILED;
|
||||
LOG_ERROR("%s graph execution failed on %s: %s", get_desc().c_str(),
|
||||
ggml_backend_name(runtime_backend), error.what());
|
||||
return std::nullopt;
|
||||
@@ -667,6 +653,7 @@ std::optional<sd::Tensor<float>> GGMLRunner::compute(get_graph_cb_t get_graph,
|
||||
success = output.has_value();
|
||||
if (success) {
|
||||
cache_.graph_end(true);
|
||||
last_compute_status_ = GGML_STATUS_SUCCESS;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -784,6 +771,7 @@ bool GGMLRunner::execute_segment(ggml_cgraph* graph, int n_threads) {
|
||||
}
|
||||
workspace_.synchronize();
|
||||
if (status != GGML_STATUS_SUCCESS) {
|
||||
last_compute_status_ = status;
|
||||
LOG_ERROR("%s compute failed: %s", get_desc().c_str(), ggml_status_to_string(status));
|
||||
return false;
|
||||
}
|
||||
@@ -836,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 full_measurement = measure(graph, cached_plan.compute_buffer_size);
|
||||
if (full_measurement.buffers.empty()) {
|
||||
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
|
||||
return std::nullopt;
|
||||
}
|
||||
auto manager = residency_manager.lock();
|
||||
@@ -906,7 +895,9 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
SegmentGraphBindings& bindings;
|
||||
ggml_context* context;
|
||||
~SegmentCleanup() {
|
||||
runner.workspace_.segment_end();
|
||||
if (!runner.workspace_.segment_end()) {
|
||||
runner.last_compute_status_ = GGML_STATUS_FAILED;
|
||||
}
|
||||
bindings.restore();
|
||||
weights.segment_end();
|
||||
ggml_free(context);
|
||||
@@ -916,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;
|
||||
if (!workspace_.prepare(measurement)) {
|
||||
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
|
||||
return fail_segment("workspace preparation");
|
||||
}
|
||||
const size_t cut_bytes = last ? 0 : cut_cache_.estimate_output_bytes(graph, segment);
|
||||
@@ -930,7 +922,11 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
sync_runtime_residency();
|
||||
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)) {
|
||||
return fail_segment("weight preparation");
|
||||
@@ -939,12 +935,17 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
if (!workspace_.measurement_matches(segment_graph, measurement)) {
|
||||
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");
|
||||
}
|
||||
if (!workspace_.allocate(segment_graph, [&](ggml_backend_sched_t scheduler, ggml_cgraph* current) {
|
||||
pin_multi_device_nodes(scheduler, current);
|
||||
})) {
|
||||
last_compute_status_ = GGML_STATUS_ALLOC_FAILED;
|
||||
return fail_segment("workspace allocation");
|
||||
}
|
||||
for (const auto& size : measurement.buffers) {
|
||||
@@ -982,6 +983,7 @@ std::optional<Tensor<float>> GGMLRunner::execute_graph(ggml_cgraph* graph, int n
|
||||
}
|
||||
}
|
||||
if (!workspace_.segment_end()) {
|
||||
last_compute_status_ = GGML_STATUS_FAILED;
|
||||
return fail_segment("workspace synchronization");
|
||||
}
|
||||
// Final outputs and their callbacks may still be views of consumed cuts.
|
||||
|
||||
+4
-16
@@ -69,8 +69,6 @@ struct GGMLRunnerContext {
|
||||
ggml_context* ggml_ctx = nullptr;
|
||||
bool flash_attn_enabled = false;
|
||||
bool sage_attn_enabled = false;
|
||||
bool sol_attn_enabled = false;
|
||||
float sol_attn_tau = 1.f;
|
||||
float linear_scale = 0.f;
|
||||
float attn_scale = 0.f;
|
||||
bool conv2d_direct_enabled = false;
|
||||
@@ -132,7 +130,8 @@ ggml_tensor* ggml_ext_attention_ext(GGMLRunnerContext* ctx,
|
||||
struct GGMLRunner {
|
||||
private:
|
||||
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);
|
||||
std::vector<DeviceMemoryRequest> memory_requests(const std::vector<sd::BackendBufferSize>& sizes,
|
||||
@@ -180,9 +179,6 @@ protected:
|
||||
|
||||
bool flash_attn_enabled = false;
|
||||
bool sage_attn_enabled = false;
|
||||
bool sol_attn_enabled = false;
|
||||
float sol_attn_tau = 1.f;
|
||||
bool sol_attn_graph_logged = false;
|
||||
float linear_scale = 0.f;
|
||||
float attn_scale = 0.f;
|
||||
bool conv2d_direct_enabled = false;
|
||||
@@ -340,6 +336,8 @@ public:
|
||||
bool no_return = false,
|
||||
const std::function<bool()>& read_outputs = {});
|
||||
|
||||
ggml_status last_compute_status() const { return last_compute_status_; }
|
||||
|
||||
void set_flash_attention_enabled(bool enabled) {
|
||||
flash_attn_enabled = enabled;
|
||||
}
|
||||
@@ -352,16 +350,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void set_sol_attention_enabled(bool enabled, float tau) {
|
||||
if (sol_attn_enabled != enabled || sol_attn_tau != tau) {
|
||||
free_cache_ctx_and_buffer();
|
||||
graph_cut_plan_cache_.graph_cut_plans.clear();
|
||||
sol_attn_enabled = enabled;
|
||||
sol_attn_tau = tau;
|
||||
sol_attn_graph_logged = false;
|
||||
}
|
||||
}
|
||||
|
||||
void set_scale_overrides(float linear_scale, float attn_scale) {
|
||||
this->linear_scale = linear_scale;
|
||||
this->attn_scale = attn_scale;
|
||||
|
||||
@@ -252,6 +252,14 @@ static inline bool sd_version_is_sensenova_u1(SDVersion version) {
|
||||
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) {
|
||||
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;
|
||||
|
||||
+14
-6
@@ -1613,7 +1613,8 @@ void ModelManager::remove_runtime_owner(uintptr_t owner_id) {
|
||||
|
||||
ModelManager::CapacityCheck ModelManager::check_capacity(
|
||||
const DeviceMemoryRequest& request,
|
||||
const std::vector<TensorState*>& states) const {
|
||||
const std::vector<TensorState*>& states,
|
||||
bool log_details) const {
|
||||
CapacityCheck result;
|
||||
if (request.compute_backend == nullptr || sd_backend_is_cpu(request.compute_backend)) {
|
||||
return result;
|
||||
@@ -1631,16 +1632,23 @@ ModelManager::CapacityCheck ModelManager::check_capacity(
|
||||
}
|
||||
size_t free_bytes = 0, total_bytes = 0;
|
||||
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) {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
// 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};
|
||||
}
|
||||
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) {
|
||||
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
|
||||
? "unknown"
|
||||
: sd_format("%.2f MB", capacity.available_device_bytes / (1024.0 * 1024.0));
|
||||
|
||||
+2
-1
@@ -157,7 +157,8 @@ private:
|
||||
}
|
||||
};
|
||||
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 split_buffer_type_for(const TensorState& state) const;
|
||||
|
||||
@@ -899,60 +899,7 @@ bool StableDiffusionGGML::set_sage_attention_enabled(bool enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StableDiffusionGGML::set_sol_attention_enabled(bool enabled, float tau) {
|
||||
if (!diffusion_model || !std::isfinite(tau)) {
|
||||
LOG_ERROR("Sol-Attn requires a diffusion model and finite tau");
|
||||
return false;
|
||||
}
|
||||
if (enabled) {
|
||||
if (config_->params.sage_attn) {
|
||||
LOG_ERROR("Sol-Attn and SageAttention cannot be enabled together");
|
||||
return false;
|
||||
}
|
||||
#ifndef SD_USE_UPSTREAM_GGML
|
||||
auto* ctx = ggml_init({4 * ggml_tensor_overhead(), nullptr, true});
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
auto* q = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, 128, 128, 1, 1);
|
||||
auto* k = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, 128, 128, 1, 1);
|
||||
auto* v = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, 128, 128, 1, 1);
|
||||
auto* op = ggml_sol_attn(ctx, q, k, v, 1.f / sqrtf(128.f), tau);
|
||||
bool supported = true;
|
||||
for (auto backend : backend_manager.runtime_backends(SDBackendModule::DIFFUSION)) {
|
||||
if (!ggml_backend_supports_op(backend, op)) {
|
||||
LOG_ERROR("Sol-Attn is unavailable on %s; it requires patched GGML, CUDA 12.0 or newer, and SM80 or newer kernels", ggml_backend_name(backend));
|
||||
supported = false;
|
||||
}
|
||||
}
|
||||
ggml_free(ctx);
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
LOG_ERROR("Sol-Attn requires -DSD_USE_UPSTREAM_GGML=OFF and a CUDA backend");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
diffusion_model->set_sol_attention_enabled(enabled, tau);
|
||||
if (high_noise_diffusion_model) {
|
||||
high_noise_diffusion_model->set_sol_attention_enabled(enabled, tau);
|
||||
}
|
||||
if (enabled) {
|
||||
LOG_INFO("Using Sol-Attn (tau=%g, diagonal threshold) in diffusion; unsupported attention uses flash/default attention", tau);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StableDiffusionGGML::init(const sd_ctx_params_t* sd_ctx_params) {
|
||||
if (sd_ctx_params->sol_attn && sd_ctx_params->sage_attn) {
|
||||
LOG_ERROR("Sol-Attn and SageAttention cannot be enabled together");
|
||||
return false;
|
||||
}
|
||||
if (!std::isfinite(sd_ctx_params->sol_attn_tau)) {
|
||||
LOG_ERROR("Sol-Attn tau must be finite");
|
||||
return false;
|
||||
}
|
||||
#ifdef SD_USE_UPSTREAM_GGML
|
||||
LOG_WARN(
|
||||
"Using upstream GGML: INT8 tensorwise/convrot is disabled and FP8 weights are "
|
||||
@@ -1233,9 +1180,6 @@ bool StableDiffusionGGML::validate_and_load_runners() {
|
||||
if (sd_ctx_params->sage_attn && !set_sage_attention_enabled(true)) {
|
||||
return false;
|
||||
}
|
||||
if (sd_ctx_params->sol_attn && !set_sol_attention_enabled(true, sd_ctx_params->sol_attn_tau)) {
|
||||
return false;
|
||||
}
|
||||
LOG_VERBOSE("validating model metadata");
|
||||
|
||||
std::set<std::string> ignore_tensors;
|
||||
@@ -2822,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);
|
||||
const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode();
|
||||
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);
|
||||
}
|
||||
return decoded;
|
||||
|
||||
@@ -313,7 +313,6 @@ public:
|
||||
|
||||
bool init(const sd_ctx_params_t* sd_ctx_params);
|
||||
bool set_sage_attention_enabled(bool enabled);
|
||||
bool set_sol_attention_enabled(bool enabled, float tau);
|
||||
|
||||
bool uses_tae() const;
|
||||
|
||||
|
||||
@@ -789,15 +789,9 @@ namespace sd::pipeline {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MiniMax-H3 is video-only. Its denoiser always splits the packed latent into a video and an
|
||||
// audio half, and only generate_video ever computes the audio length, so reaching this
|
||||
// function with an H3 checkpoint is guaranteed to die on
|
||||
// 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");
|
||||
if (!sd_version_supports_image_generation(sd->version)) {
|
||||
LOG_ERROR("%s cannot be run with generate_image(); use generate_video() or --mode vid_gen in the CLI",
|
||||
model_version_to_str[sd->version]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -338,8 +338,6 @@ void sd_ctx_params_init(sd_ctx_params_t* sd_ctx_params) {
|
||||
sd_ctx_params->enable_mmap = false;
|
||||
sd_ctx_params->diffusion_flash_attn = false;
|
||||
sd_ctx_params->sage_attn = false;
|
||||
sd_ctx_params->sol_attn = false;
|
||||
sd_ctx_params->sol_attn_tau = 1.f;
|
||||
sd_ctx_params->linear_scale = 0.f;
|
||||
sd_ctx_params->attn_scale = 0.f;
|
||||
sd_ctx_params->vae_format = SD_VAE_FORMAT_AUTO;
|
||||
@@ -396,8 +394,6 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
|
||||
"flash_attn: %s\n"
|
||||
"diffusion_flash_attn: %s\n"
|
||||
"sage_attn: %s\n"
|
||||
"sol_attn: %s\n"
|
||||
"sol_attn_tau: %g\n"
|
||||
"linear_scale: %g\n"
|
||||
"attn_scale: %g\n"
|
||||
"vae_format: %s\n",
|
||||
@@ -438,8 +434,6 @@ char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) {
|
||||
BOOL_STR(sd_ctx_params->flash_attn),
|
||||
BOOL_STR(sd_ctx_params->diffusion_flash_attn),
|
||||
BOOL_STR(sd_ctx_params->sage_attn),
|
||||
BOOL_STR(sd_ctx_params->sol_attn),
|
||||
sd_ctx_params->sol_attn_tau,
|
||||
sd_ctx_params->linear_scale,
|
||||
sd_ctx_params->attn_scale,
|
||||
sd_vae_format_name(sd_ctx_params->vae_format));
|
||||
@@ -636,14 +630,6 @@ struct sd_ctx_t {
|
||||
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* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t));
|
||||
if (sd_ctx == nullptr) {
|
||||
|
||||
Reference in New Issue
Block a user