mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-09-25 04:32:29 +00:00
feat: generalize temporal tiling across video VAEs (#1926)
This commit is contained in:
parent
40e605f3f1
commit
6b3edaaf32
@ -1013,7 +1013,7 @@ ArgOptions SDGenerationParams::get_options() {
|
|||||||
&extra_sample_args},
|
&extra_sample_args},
|
||||||
{"",
|
{"",
|
||||||
"--extra-tiling-args",
|
"--extra-tiling-args",
|
||||||
"extra VAE tiling args, key=value list. LTX video VAE supports temporal_tile_frames (default: 4), temporal_tile_overlap (default: 1)",
|
"extra VAE tiling args, key=value list. Supported video VAEs accept temporal_tile_frames/temporal_tile_size (default: 4), temporal_tile_overlap (default: 1)",
|
||||||
(int)',',
|
(int)',',
|
||||||
&extra_tiling_args},
|
&extra_tiling_args},
|
||||||
{"",
|
{"",
|
||||||
@ -1230,7 +1230,7 @@ ArgOptions SDGenerationParams::get_options() {
|
|||||||
&vae_tiling_params.enabled},
|
&vae_tiling_params.enabled},
|
||||||
{"",
|
{"",
|
||||||
"--temporal-tiling",
|
"--temporal-tiling",
|
||||||
"enable temporal tiling for LTX video VAE decode",
|
"enable temporal tiling for supported video VAE decode",
|
||||||
true,
|
true,
|
||||||
&vae_tiling_params.temporal_tiling},
|
&vae_tiling_params.temporal_tiling},
|
||||||
{"",
|
{"",
|
||||||
|
|||||||
@ -518,7 +518,8 @@ Shared default fields used by both `img_gen` and `vid_gen`:
|
|||||||
| `output_format` | `string` |
|
| `output_format` | `string` |
|
||||||
| `output_compression` | `integer` |
|
| `output_compression` | `integer` |
|
||||||
|
|
||||||
`vae_tiling_params.extra_tiling_args` accepts a key=value list. For LTX video VAE temporal tiling, `temporal_tile_frames` defaults to `4` and `temporal_tile_overlap` defaults to `1`.
|
`vae_tiling_params.extra_tiling_args` accepts a key=value list. Supported video VAEs accept `temporal_tile_frames` (alias `temporal_tile_size`, default `4`) and `temporal_tile_overlap` (default `1`).
|
||||||
|
LTX and Wan preserve causal state between temporal tiles. Hunyuan Video and TAEHV use overlap blending. MiniMax H3 keeps its model-specific fixed temporal windows because its latent-to-frame mapping is non-linear.
|
||||||
|
|
||||||
`img_gen`-specific default fields:
|
`img_gen`-specific default fields:
|
||||||
|
|
||||||
|
|||||||
@ -364,15 +364,11 @@ namespace sd::backend_fit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
if (prefer_temporal_tiling) {
|
const char* retry_mode = nullptr;
|
||||||
if (tiling_params.temporal_tiling) {
|
if (prefer_temporal_tiling && !tiling_params.temporal_tiling) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
tiling_params.temporal_tiling = true;
|
tiling_params.temporal_tiling = true;
|
||||||
} else {
|
retry_mode = tiling_params.enabled ? "spatial+temporal" : "temporal";
|
||||||
if (tiling_params.enabled) {
|
} else if (!tiling_params.enabled) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
tiling_params.enabled = true;
|
tiling_params.enabled = true;
|
||||||
if (tiling_params.tile_size_x <= 0) {
|
if (tiling_params.tile_size_x <= 0) {
|
||||||
tiling_params.tile_size_x = 256;
|
tiling_params.tile_size_x = 256;
|
||||||
@ -380,10 +376,13 @@ namespace sd::backend_fit {
|
|||||||
if (tiling_params.tile_size_y <= 0) {
|
if (tiling_params.tile_size_y <= 0) {
|
||||||
tiling_params.tile_size_y = 256;
|
tiling_params.tile_size_y = 256;
|
||||||
}
|
}
|
||||||
|
retry_mode = tiling_params.temporal_tiling ? "spatial+temporal" : "spatial";
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WARN("auto-fit: VAE decode failed (likely out of memory); retrying with %s tiling",
|
LOG_WARN("auto-fit: VAE decode failed (likely out of memory); retrying with %s tiling",
|
||||||
tiling_params.temporal_tiling ? "temporal" : "spatial");
|
retry_mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -758,6 +758,15 @@ namespace Hunyuan {
|
|||||||
return "hunyuan_video_vae";
|
return "hunyuan_video_vae";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool supports_temporal_tiling(VAETemporalDirection direction) const override {
|
||||||
|
return direction == VAETemporalDirection::DECODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_temporal_tile_output_scale(VAETemporalDirection direction) const override {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||||
if (!decode_only) {
|
if (!decode_only) {
|
||||||
encoder.get_param_tensors(tensors, weight_prefix + ".encoder");
|
encoder.get_param_tensors(tensors, weight_prefix + ".encoder");
|
||||||
|
|||||||
@ -1213,9 +1213,6 @@ struct LTXVideoVAE : public VAE {
|
|||||||
static constexpr int DEFAULT_TEMPORAL_TILE_OVERLAP = 1;
|
static constexpr int DEFAULT_TEMPORAL_TILE_OVERLAP = 1;
|
||||||
|
|
||||||
bool decode_only;
|
bool decode_only;
|
||||||
bool temporal_tiling_enabled = false;
|
|
||||||
int temporal_tile_frames = DEFAULT_TEMPORAL_TILE_FRAMES;
|
|
||||||
int temporal_tile_overlap = DEFAULT_TEMPORAL_TILE_OVERLAP;
|
|
||||||
int ltx_vae_version;
|
int ltx_vae_version;
|
||||||
bool timestep_conditioning;
|
bool timestep_conditioning;
|
||||||
int patch_size;
|
int patch_size;
|
||||||
@ -1248,64 +1245,24 @@ struct LTXVideoVAE : public VAE {
|
|||||||
return "ltx_video_vae";
|
return "ltx_video_vae";
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_temporal_tiling_enabled(bool enabled) override {
|
bool supports_temporal_tiling(VAETemporalDirection direction) const override {
|
||||||
temporal_tiling_enabled = enabled;
|
return direction == VAETemporalDirection::DECODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_tiling_params(const sd_tiling_params_t& params) override {
|
int get_default_temporal_tile_frames(VAETemporalDirection direction) const override {
|
||||||
temporal_tiling_enabled = params.temporal_tiling;
|
SD_UNUSED(direction);
|
||||||
temporal_tile_frames = DEFAULT_TEMPORAL_TILE_FRAMES;
|
return DEFAULT_TEMPORAL_TILE_FRAMES;
|
||||||
temporal_tile_overlap = DEFAULT_TEMPORAL_TILE_OVERLAP;
|
}
|
||||||
|
|
||||||
for (const auto& [key, value] : parse_key_value_args(params.extra_tiling_args, "LTX VAE extra tiling arg")) {
|
int get_default_temporal_tile_overlap(VAETemporalDirection direction) const override {
|
||||||
int parsed = 0;
|
SD_UNUSED(direction);
|
||||||
if (!parse_strict_int(value, parsed)) {
|
return DEFAULT_TEMPORAL_TILE_OVERLAP;
|
||||||
LOG_WARN("ignoring invalid LTX VAE extra tiling arg '%s=%s'", key.c_str(), value.c_str());
|
|
||||||
} else if (key == "temporal_tile_frames") {
|
|
||||||
temporal_tile_frames = std::max(1, parsed);
|
|
||||||
} else if (key == "temporal_tile_overlap") {
|
|
||||||
temporal_tile_overlap = std::max(0, parsed);
|
|
||||||
} else {
|
|
||||||
LOG_WARN("ignoring unknown LTX VAE extra tiling arg '%s'", key.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||||
vae.get_param_tensors(tensors, weight_prefix);
|
vae.get_param_tensors(tensors, weight_prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TemporalTilePlan {
|
|
||||||
int frames = 1;
|
|
||||||
int overlap = 0;
|
|
||||||
int stride = 1;
|
|
||||||
int num_tiles = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
TemporalTilePlan resolve_temporal_tile_plan(int64_t total_frames) const {
|
|
||||||
TemporalTilePlan plan;
|
|
||||||
plan.frames = std::max(1, temporal_tile_frames);
|
|
||||||
plan.overlap = std::max(0, temporal_tile_overlap);
|
|
||||||
|
|
||||||
if (plan.overlap >= plan.frames) {
|
|
||||||
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to temporal_tile_frames (%d), adjusting values to avoid empty decode windows",
|
|
||||||
plan.overlap,
|
|
||||||
plan.frames);
|
|
||||||
plan.overlap = plan.frames - 1;
|
|
||||||
}
|
|
||||||
if (total_frames > 1 && plan.overlap >= total_frames) {
|
|
||||||
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to total latent frames (%lld), adjusting values to decode at least one tile",
|
|
||||||
plan.overlap,
|
|
||||||
(long long)total_frames);
|
|
||||||
plan.overlap = static_cast<int>(total_frames - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
plan.stride = std::max(1, plan.frames - plan.overlap);
|
|
||||||
int64_t tiled_frames = std::max<int64_t>(1, total_frames - plan.overlap);
|
|
||||||
plan.num_tiles = total_frames > 0 ? static_cast<int>((tiled_frames + plan.stride - 1) / plan.stride) : 0;
|
|
||||||
return plan;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string temporal_feat_cache_name(size_t feat_idx) const {
|
std::string temporal_feat_cache_name(size_t feat_idx) const {
|
||||||
return "ltx_vae_temporal_feat:" + std::to_string(feat_idx);
|
return "ltx_vae_temporal_feat:" + std::to_string(feat_idx);
|
||||||
}
|
}
|
||||||
@ -1365,52 +1322,53 @@ struct LTXVideoVAE : public VAE {
|
|||||||
|
|
||||||
sd::Tensor<float> decode_temporal_tiled_streaming(const int n_threads,
|
sd::Tensor<float> decode_temporal_tiled_streaming(const int n_threads,
|
||||||
const sd::Tensor<float>& input,
|
const sd::Tensor<float>& input,
|
||||||
size_t expected_dim) {
|
size_t expected_dim,
|
||||||
|
const VAETemporalTilingConfig& config) {
|
||||||
const int64_t total_frames = input.shape()[2];
|
const int64_t total_frames = input.shape()[2];
|
||||||
TemporalTilePlan plan = resolve_temporal_tile_plan(total_frames);
|
auto plan = make_vae_temporal_tile_plan(total_frames, config);
|
||||||
|
|
||||||
LOG_DEBUG("Using streaming temporal tiling: temporal_tile_frames=%d, temporal_tile_overlap=%d, total latent frames=%lld, resulting in %d tiles",
|
LOG_DEBUG("Using streaming temporal tiling: temporal_tile_frames=%d, temporal_tile_overlap=%d, total latent frames=%lld, resulting in %d tiles",
|
||||||
plan.frames,
|
plan.tile_frames,
|
||||||
plan.overlap,
|
plan.overlap,
|
||||||
(long long)total_frames,
|
(long long)total_frames,
|
||||||
plan.num_tiles);
|
(int)plan.tiles.size());
|
||||||
|
|
||||||
free_cache_ctx_and_buffer();
|
free_cache_ctx_and_buffer();
|
||||||
cache_tensor_map.clear();
|
cache_tensor_map.clear();
|
||||||
|
|
||||||
sd::Tensor<float> output;
|
auto output = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& z_chunk, const VAETemporalTile& tile) {
|
||||||
for (int64_t start = 0; start < total_frames - plan.overlap; start += plan.stride) {
|
|
||||||
const int64_t end = std::min<int64_t>(total_frames, start + plan.frames);
|
|
||||||
const int chunk_overlap = end < total_frames ? plan.overlap : 0;
|
|
||||||
auto z_chunk = sd::ops::slice(input, 2, start, end);
|
|
||||||
|
|
||||||
LOG_DEBUG("LTX VAE temporal tile %lld/%d: latent frames [%lld, %lld), overlap=%d",
|
LOG_DEBUG("LTX VAE temporal tile %lld/%d: latent frames [%lld, %lld), overlap=%d",
|
||||||
(long long)(start / plan.stride + 1),
|
(long long)tile.index + 1,
|
||||||
plan.num_tiles,
|
(int)plan.tiles.size(),
|
||||||
(long long)start,
|
(long long)tile.start,
|
||||||
(long long)end,
|
(long long)tile.end,
|
||||||
chunk_overlap);
|
tile.overlap);
|
||||||
|
|
||||||
auto get_graph = [&]() -> ggml_cgraph* {
|
auto get_graph = [&]() -> ggml_cgraph* {
|
||||||
return build_temporal_tile_graph(z_chunk,
|
return build_temporal_tile_graph(z_chunk,
|
||||||
static_cast<int>(start),
|
static_cast<int>(tile.start),
|
||||||
chunk_overlap);
|
tile.overlap);
|
||||||
};
|
};
|
||||||
auto chunk = restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, true, true, true),
|
return restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, true, true, true),
|
||||||
expected_dim);
|
expected_dim);
|
||||||
if (chunk.empty()) {
|
});
|
||||||
free_cache_ctx_and_buffer();
|
|
||||||
cache_tensor_map.clear();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
output = output.empty() ? std::move(chunk) : sd::ops::concat(output, chunk, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_cache_ctx_and_buffer();
|
free_cache_ctx_and_buffer();
|
||||||
cache_tensor_map.clear();
|
cache_tensor_map.clear();
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sd::Tensor<float> _compute_temporal_tiled(const int n_threads,
|
||||||
|
const sd::Tensor<float>& input,
|
||||||
|
VAETemporalDirection direction,
|
||||||
|
const VAETemporalTilingConfig& config) override {
|
||||||
|
GGML_ASSERT(direction == VAETemporalDirection::DECODE);
|
||||||
|
return decode_temporal_tiled_streaming(n_threads,
|
||||||
|
input,
|
||||||
|
static_cast<size_t>(input.dim()),
|
||||||
|
config);
|
||||||
|
}
|
||||||
|
|
||||||
ggml_cgraph* build_latent_statistics_graph(const sd::Tensor<float>& z_tensor, bool normalize) {
|
ggml_cgraph* build_latent_statistics_graph(const sd::Tensor<float>& z_tensor, bool normalize) {
|
||||||
ggml_cgraph* gf = new_graph_custom(1024);
|
ggml_cgraph* gf = new_graph_custom(1024);
|
||||||
ggml_tensor* z = make_input(z_tensor);
|
ggml_tensor* z = make_input(z_tensor);
|
||||||
@ -1446,9 +1404,6 @@ struct LTXVideoVAE : public VAE {
|
|||||||
input = sd::ops::slice(input, 2, 0, cropped_t);
|
input = sd::ops::slice(input, 2, 0, cropped_t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (decode_graph && temporal_tiling_enabled && input.dim() == 5 && input.shape()[2] > 1) {
|
|
||||||
return decode_temporal_tiled_streaming(n_threads, input, expected_dim);
|
|
||||||
}
|
|
||||||
auto get_graph = [&]() -> ggml_cgraph* {
|
auto get_graph = [&]() -> ggml_cgraph* {
|
||||||
return build_graph(input, decode_graph);
|
return build_graph(input, decode_graph);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -559,6 +559,7 @@ namespace MiniMaxH3VAE {
|
|||||||
|
|
||||||
static sd_tiling_params_t h3_tiling(sd_tiling_params_t params) {
|
static sd_tiling_params_t h3_tiling(sd_tiling_params_t params) {
|
||||||
params.enabled = true;
|
params.enabled = true;
|
||||||
|
params.temporal_tiling = false;
|
||||||
params.tile_size_x = 16;
|
params.tile_size_x = 16;
|
||||||
params.tile_size_y = 16;
|
params.tile_size_y = 16;
|
||||||
params.target_overlap = 0.25f;
|
params.target_overlap = 0.25f;
|
||||||
@ -624,16 +625,14 @@ namespace MiniMaxH3VAE {
|
|||||||
if (pad > 0) {
|
if (pad > 0) {
|
||||||
input = repeat_last_frame(input, pad);
|
input = repeat_last_frame(input, pad);
|
||||||
}
|
}
|
||||||
sd::Tensor<float> result;
|
auto plan = make_vae_temporal_tile_plan(input.shape()[2], {17, 0});
|
||||||
for (int64_t start = 0; start < input.shape()[2]; start += 17) {
|
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||||
auto chunk = sd::ops::slice(input, 2, start, start + 17);
|
SD_UNUSED(tile);
|
||||||
auto encoded = VAE::encode(n_threads, chunk, tiling, circular_x, circular_y);
|
return VAE::encode(n_threads, chunk, tiling, circular_x, circular_y);
|
||||||
if (encoded.empty()) {
|
});
|
||||||
|
if (result.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
result = result.empty() ? std::move(encoded)
|
|
||||||
: sd::ops::concat(result, encoded, 2);
|
|
||||||
}
|
|
||||||
if (result.shape()[2] > 3) {
|
if (result.shape()[2] > 3) {
|
||||||
result = sd::ops::slice(result, 2, 0, result.shape()[2] - 3);
|
result = sd::ops::slice(result, 2, 0, result.shape()[2] - 3);
|
||||||
}
|
}
|
||||||
@ -685,13 +684,12 @@ namespace MiniMaxH3VAE {
|
|||||||
input = repeat_last_frame(input, pad_tokens);
|
input = repeat_last_frame(input, pad_tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
sd::Tensor<float> result;
|
|
||||||
sd::Tensor<float> overlap;
|
sd::Tensor<float> overlap;
|
||||||
for (int64_t i = 0; i < num_chunks; ++i) {
|
auto plan = make_vae_temporal_tile_plan(
|
||||||
int64_t start = i * tokens_per_chunk;
|
input.shape()[2],
|
||||||
int64_t end = std::min(start + tokens_per_chunk + token_overlap,
|
{static_cast<int>(tokens_per_chunk + token_overlap), static_cast<int>(token_overlap)});
|
||||||
input.shape()[2]);
|
GGML_ASSERT(plan.tiles.size() == static_cast<size_t>(num_chunks));
|
||||||
auto chunk = sd::ops::slice(input, 2, start, end);
|
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||||
auto decoded = VAE::decode(n_threads,
|
auto decoded = VAE::decode(n_threads,
|
||||||
chunk,
|
chunk,
|
||||||
tiling,
|
tiling,
|
||||||
@ -700,7 +698,7 @@ namespace MiniMaxH3VAE {
|
|||||||
circular_y,
|
circular_y,
|
||||||
silent);
|
silent);
|
||||||
if (decoded.empty()) {
|
if (decoded.empty()) {
|
||||||
return {};
|
return sd::Tensor<float>();
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t first_end = std::min<int64_t>(frames_per_chunk, decoded.shape()[2]);
|
int64_t first_end = std::min<int64_t>(frames_per_chunk, decoded.shape()[2]);
|
||||||
@ -712,8 +710,6 @@ namespace MiniMaxH3VAE {
|
|||||||
first = blend_temporal(overlap, first, frame_overlap);
|
first = blend_temporal(overlap, first, frame_overlap);
|
||||||
overlap = {};
|
overlap = {};
|
||||||
}
|
}
|
||||||
result = result.empty() ? std::move(first)
|
|
||||||
: sd::ops::concat(result, first, 2);
|
|
||||||
|
|
||||||
if (decoded.shape()[2] > frames_per_chunk + frame_pre_padding) {
|
if (decoded.shape()[2] > frames_per_chunk + frame_pre_padding) {
|
||||||
overlap = sd::ops::slice(decoded,
|
overlap = sd::ops::slice(decoded,
|
||||||
@ -721,10 +717,14 @@ namespace MiniMaxH3VAE {
|
|||||||
frames_per_chunk + frame_pre_padding,
|
frames_per_chunk + frame_pre_padding,
|
||||||
decoded.shape()[2]);
|
decoded.shape()[2]);
|
||||||
}
|
}
|
||||||
if (i == num_chunks - 1 && !overlap.empty()) {
|
if (tile.last && !overlap.empty()) {
|
||||||
result = sd::ops::concat(result, overlap, 2);
|
first = sd::ops::concat(first, overlap, 2);
|
||||||
overlap = {};
|
overlap = {};
|
||||||
}
|
}
|
||||||
|
return first;
|
||||||
|
});
|
||||||
|
if (result.empty()) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t expected_frames = input.shape()[2] <= 1 ? 1 : ((x.shape()[2] - 2) / 5) * 17 + 5;
|
int64_t expected_frames = input.shape()[2] <= 1 ? 1 : ((x.shape()[2] - 2) / 5) * 17 + 5;
|
||||||
|
|||||||
@ -819,6 +819,21 @@ struct TinyVideoAutoEncoder : public VAE {
|
|||||||
return "taehv";
|
return "taehv";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool supports_temporal_tiling(VAETemporalDirection direction) const override {
|
||||||
|
return direction == VAETemporalDirection::DECODE && !sd_version_is_minimax_h3(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_temporal_tile_output_scale(VAETemporalDirection direction) const override {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
int scale = 1;
|
||||||
|
for (bool upscale : taehv.time_upscale) {
|
||||||
|
if (upscale) {
|
||||||
|
scale *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||||
taehv.get_param_tensors(tensors, weight_prefix);
|
taehv.get_param_tensors(tensors, weight_prefix);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "core/tensor_ggml.hpp"
|
#include "core/tensor_ggml.hpp"
|
||||||
#include "model/common/block.hpp"
|
#include "model/common/block.hpp"
|
||||||
|
#include "model/vae/vae_tiling.hpp"
|
||||||
#include "model_manager.h"
|
#include "model_manager.h"
|
||||||
|
|
||||||
struct VAE : public GGMLRunner {
|
struct VAE : public GGMLRunner {
|
||||||
@ -14,6 +15,87 @@ protected:
|
|||||||
const sd::Tensor<float>& z,
|
const sd::Tensor<float>& z,
|
||||||
bool decode_graph) = 0;
|
bool decode_graph) = 0;
|
||||||
|
|
||||||
|
virtual bool supports_temporal_tiling(VAETemporalDirection direction) const {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int get_default_temporal_tile_frames(VAETemporalDirection direction) const {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int get_default_temporal_tile_overlap(VAETemporalDirection direction) const {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int get_temporal_tile_output_scale(VAETemporalDirection direction) const {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual sd::Tensor<float> _compute_temporal_tiled(const int n_threads,
|
||||||
|
const sd::Tensor<float>& input,
|
||||||
|
VAETemporalDirection direction,
|
||||||
|
const VAETemporalTilingConfig& config) {
|
||||||
|
if (direction != VAETemporalDirection::DECODE) {
|
||||||
|
return _compute(n_threads, input, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
VAETemporalTilingConfig resolved_config = config;
|
||||||
|
const int output_scale = get_temporal_tile_output_scale(direction);
|
||||||
|
if (output_scale > 1 &&
|
||||||
|
resolved_config.overlap == 0 &&
|
||||||
|
input.shape()[2] > resolved_config.tile_frames) {
|
||||||
|
LOG_WARN("%s temporal decode requires at least one overlapping latent frame; using overlap=1",
|
||||||
|
get_desc().c_str());
|
||||||
|
resolved_config.overlap = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto plan = make_vae_temporal_tile_plan(input.shape()[2], resolved_config);
|
||||||
|
LOG_DEBUG("%s temporal tiling: tile_frames=%d, overlap=%d, total_frames=%lld, tiles=%d",
|
||||||
|
get_desc().c_str(),
|
||||||
|
plan.tile_frames,
|
||||||
|
plan.overlap,
|
||||||
|
(long long)input.shape()[2],
|
||||||
|
(int)plan.tiles.size());
|
||||||
|
return process_vae_temporal_tiles_blended(
|
||||||
|
input,
|
||||||
|
plan,
|
||||||
|
output_scale,
|
||||||
|
[&](const sd::Tensor<float>& input_tile, const VAETemporalTile& tile) {
|
||||||
|
LOG_DEBUG("%s temporal tile %d/%d: input frames [%lld, %lld)",
|
||||||
|
get_desc().c_str(),
|
||||||
|
tile.index + 1,
|
||||||
|
(int)plan.tiles.size(),
|
||||||
|
(long long)tile.start,
|
||||||
|
(long long)tile.end);
|
||||||
|
return _compute(n_threads, input_tile, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sd::Tensor<float> compute_with_temporal_tiling(const int n_threads,
|
||||||
|
const sd::Tensor<float>& input,
|
||||||
|
VAETemporalDirection direction,
|
||||||
|
const sd_tiling_params_t& tiling_params) {
|
||||||
|
if (!tiling_params.temporal_tiling || input.dim() != 5 || input.shape()[2] <= 1) {
|
||||||
|
return _compute(n_threads, input, direction == VAETemporalDirection::DECODE);
|
||||||
|
}
|
||||||
|
if (!supports_temporal_tiling(direction)) {
|
||||||
|
LOG_WARN("%s does not support temporal tiling for %s; processing the full temporal dimension",
|
||||||
|
get_desc().c_str(),
|
||||||
|
direction == VAETemporalDirection::DECODE ? "decode" : "encode");
|
||||||
|
return _compute(n_threads, input, direction == VAETemporalDirection::DECODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = resolve_vae_temporal_tiling_config(
|
||||||
|
tiling_params,
|
||||||
|
get_default_temporal_tile_frames(direction),
|
||||||
|
get_default_temporal_tile_overlap(direction));
|
||||||
|
return _compute_temporal_tiled(n_threads, input, direction, config);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void scale_tensor_to_minus1_1(sd::Tensor<float>* tensor) {
|
static inline void scale_tensor_to_minus1_1(sd::Tensor<float>* tensor) {
|
||||||
GGML_ASSERT(tensor != nullptr);
|
GGML_ASSERT(tensor != nullptr);
|
||||||
for (int64_t i = 0; i < tensor->numel(); ++i) {
|
for (int64_t i = 0; i < tensor->numel(); ++i) {
|
||||||
@ -40,10 +122,15 @@ protected:
|
|||||||
bool circular_x,
|
bool circular_x,
|
||||||
bool circular_y,
|
bool circular_y,
|
||||||
bool decode_graph,
|
bool decode_graph,
|
||||||
|
const sd_tiling_params_t& tiling_params,
|
||||||
const char* error_message,
|
const char* error_message,
|
||||||
bool silent = false) {
|
bool silent = false) {
|
||||||
auto on_processing = [&](const sd::Tensor<float>& input_tile) {
|
auto on_processing = [&](const sd::Tensor<float>& input_tile) {
|
||||||
auto output_tile = _compute(n_threads, input_tile, decode_graph);
|
auto output_tile = compute_with_temporal_tiling(
|
||||||
|
n_threads,
|
||||||
|
input_tile,
|
||||||
|
decode_graph ? VAETemporalDirection::DECODE : VAETemporalDirection::ENCODE,
|
||||||
|
tiling_params);
|
||||||
if (output_tile.empty()) {
|
if (output_tile.empty()) {
|
||||||
LOG_ERROR("%s", error_message);
|
LOG_ERROR("%s", error_message);
|
||||||
return sd::Tensor<float>();
|
return sd::Tensor<float>();
|
||||||
@ -86,6 +173,10 @@ public:
|
|||||||
|
|
||||||
virtual int get_encoder_output_channels(int input_channels) = 0;
|
virtual int get_encoder_output_channels(int input_channels) = 0;
|
||||||
|
|
||||||
|
bool can_temporal_tile_decode() const {
|
||||||
|
return supports_temporal_tiling(VAETemporalDirection::DECODE);
|
||||||
|
}
|
||||||
|
|
||||||
void get_tile_sizes(int& tile_size_x,
|
void get_tile_sizes(int& tile_size_x,
|
||||||
int& tile_size_y,
|
int& tile_size_y,
|
||||||
float& tile_overlap,
|
float& tile_overlap,
|
||||||
@ -151,9 +242,13 @@ public:
|
|||||||
circular_x,
|
circular_x,
|
||||||
circular_y,
|
circular_y,
|
||||||
false,
|
false,
|
||||||
|
tiling_params,
|
||||||
"vae encode compute failed while processing a tile");
|
"vae encode compute failed while processing a tile");
|
||||||
} else {
|
} else {
|
||||||
output = _compute(n_threads, input, false);
|
output = compute_with_temporal_tiling(n_threads,
|
||||||
|
input,
|
||||||
|
VAETemporalDirection::ENCODE,
|
||||||
|
tiling_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
runner_done();
|
runner_done();
|
||||||
@ -177,7 +272,6 @@ public:
|
|||||||
int64_t t0 = ggml_time_ms();
|
int64_t t0 = ggml_time_ms();
|
||||||
sd::Tensor<float> input = x;
|
sd::Tensor<float> input = x;
|
||||||
sd::Tensor<float> output;
|
sd::Tensor<float> output;
|
||||||
set_tiling_params(tiling_params);
|
|
||||||
|
|
||||||
if (tiling_params.enabled) {
|
if (tiling_params.enabled) {
|
||||||
const int scale_factor = get_scale_factor();
|
const int scale_factor = get_scale_factor();
|
||||||
@ -201,10 +295,14 @@ public:
|
|||||||
circular_x,
|
circular_x,
|
||||||
circular_y,
|
circular_y,
|
||||||
true,
|
true,
|
||||||
|
tiling_params,
|
||||||
"vae decode compute failed while processing a tile",
|
"vae decode compute failed while processing a tile",
|
||||||
silent);
|
silent);
|
||||||
} else {
|
} else {
|
||||||
output = _compute(n_threads, input, true);
|
output = compute_with_temporal_tiling(n_threads,
|
||||||
|
input,
|
||||||
|
VAETemporalDirection::DECODE,
|
||||||
|
tiling_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
runner_done();
|
runner_done();
|
||||||
@ -226,10 +324,6 @@ public:
|
|||||||
virtual sd::Tensor<float> vae_to_diffusion_latents(const sd::Tensor<float>& latents) = 0;
|
virtual sd::Tensor<float> vae_to_diffusion_latents(const sd::Tensor<float>& latents) = 0;
|
||||||
virtual void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) = 0;
|
virtual void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) = 0;
|
||||||
virtual void set_conv2d_scale(float scale) { SD_UNUSED(scale); };
|
virtual void set_conv2d_scale(float scale) { SD_UNUSED(scale); };
|
||||||
virtual void set_temporal_tiling_enabled(bool enabled) { SD_UNUSED(enabled); };
|
|
||||||
virtual void set_tiling_params(const sd_tiling_params_t& params) {
|
|
||||||
set_temporal_tiling_enabled(params.temporal_tiling);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FakeVAE : public VAE {
|
struct FakeVAE : public VAE {
|
||||||
|
|||||||
213
src/model/vae/vae_tiling.hpp
Normal file
213
src/model/vae/vae_tiling.hpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#ifndef __SD_MODEL_VAE_VAE_TILING_HPP__
|
||||||
|
#define __SD_MODEL_VAE_VAE_TILING_HPP__
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/tensor.hpp"
|
||||||
|
#include "core/util.h"
|
||||||
|
|
||||||
|
enum class VAETemporalDirection {
|
||||||
|
ENCODE,
|
||||||
|
DECODE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VAETemporalTilingConfig {
|
||||||
|
int tile_frames = 1;
|
||||||
|
int overlap = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VAETemporalTile {
|
||||||
|
int index = 0;
|
||||||
|
int64_t start = 0;
|
||||||
|
int64_t end = 0;
|
||||||
|
int overlap = 0;
|
||||||
|
bool first = false;
|
||||||
|
bool last = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VAETemporalTilePlan {
|
||||||
|
int tile_frames = 1;
|
||||||
|
int overlap = 0;
|
||||||
|
int stride = 1;
|
||||||
|
std::vector<VAETemporalTile> tiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline VAETemporalTilingConfig resolve_vae_temporal_tiling_config(const sd_tiling_params_t& params,
|
||||||
|
int default_tile_frames,
|
||||||
|
int default_overlap) {
|
||||||
|
VAETemporalTilingConfig config;
|
||||||
|
config.tile_frames = std::max(1, default_tile_frames);
|
||||||
|
config.overlap = std::max(0, default_overlap);
|
||||||
|
|
||||||
|
for (const auto& [key, value] : parse_key_value_args(params.extra_tiling_args, "VAE extra tiling arg")) {
|
||||||
|
if (key != "temporal_tile_frames" && key != "temporal_tile_size" && key != "temporal_tile_overlap") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parsed = 0;
|
||||||
|
if (!parse_strict_int(value, parsed)) {
|
||||||
|
LOG_WARN("ignoring invalid VAE extra tiling arg '%s=%s'", key.c_str(), value.c_str());
|
||||||
|
} else if (key == "temporal_tile_overlap") {
|
||||||
|
config.overlap = std::max(0, parsed);
|
||||||
|
} else {
|
||||||
|
config.tile_frames = std::max(1, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VAETemporalTilePlan make_vae_temporal_tile_plan(int64_t total_frames,
|
||||||
|
const VAETemporalTilingConfig& config) {
|
||||||
|
VAETemporalTilePlan plan;
|
||||||
|
plan.tile_frames = std::max(1, config.tile_frames);
|
||||||
|
plan.overlap = std::max(0, config.overlap);
|
||||||
|
if (total_frames <= 1) {
|
||||||
|
plan.overlap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.overlap >= plan.tile_frames) {
|
||||||
|
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to temporal_tile_frames (%d), adjusting values to avoid empty decode windows",
|
||||||
|
plan.overlap,
|
||||||
|
plan.tile_frames);
|
||||||
|
plan.overlap = plan.tile_frames - 1;
|
||||||
|
}
|
||||||
|
if (total_frames > 1 && plan.overlap >= total_frames) {
|
||||||
|
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to total frames (%lld), adjusting values to process at least one tile",
|
||||||
|
plan.overlap,
|
||||||
|
(long long)total_frames);
|
||||||
|
plan.overlap = static_cast<int>(total_frames - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.stride = std::max(1, plan.tile_frames - plan.overlap);
|
||||||
|
for (int64_t start = 0; start < total_frames - plan.overlap; start += plan.stride) {
|
||||||
|
VAETemporalTile tile;
|
||||||
|
tile.index = static_cast<int>(plan.tiles.size());
|
||||||
|
tile.start = start;
|
||||||
|
tile.end = std::min<int64_t>(total_frames, start + plan.tile_frames);
|
||||||
|
tile.overlap = tile.end < total_frames ? plan.overlap : 0;
|
||||||
|
tile.first = start == 0;
|
||||||
|
tile.last = tile.end == total_frames;
|
||||||
|
plan.tiles.push_back(tile);
|
||||||
|
}
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
inline sd::Tensor<float> process_vae_temporal_tiles(const sd::Tensor<float>& input,
|
||||||
|
const VAETemporalTilePlan& plan,
|
||||||
|
Fn&& on_processing) {
|
||||||
|
sd::Tensor<float> output;
|
||||||
|
for (const auto& tile : plan.tiles) {
|
||||||
|
auto input_tile = sd::ops::slice(input, 2, tile.start, tile.end);
|
||||||
|
auto output_tile = on_processing(input_tile, tile);
|
||||||
|
if (output_tile.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
output = output.empty() ? std::move(output_tile)
|
||||||
|
: sd::ops::concat(output, output_tile, 2);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
inline sd::Tensor<float> process_vae_temporal_tiles_blended(const sd::Tensor<float>& input,
|
||||||
|
const VAETemporalTilePlan& plan,
|
||||||
|
int output_scale,
|
||||||
|
Fn&& on_processing) {
|
||||||
|
GGML_ASSERT(output_scale >= 1);
|
||||||
|
const int64_t output_frames = 1 + (input.shape()[2] - 1) * output_scale;
|
||||||
|
const int overlap_frames = plan.overlap > 0 ? 1 + (plan.overlap - 1) * output_scale : 0;
|
||||||
|
std::vector<float> weights(static_cast<size_t>(output_frames), 0.f);
|
||||||
|
sd::Tensor<float> output;
|
||||||
|
|
||||||
|
auto smootherstep = [](float value) {
|
||||||
|
return value * value * value * (value * (value * 6.f - 15.f) + 10.f);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& tile : plan.tiles) {
|
||||||
|
auto input_tile = sd::ops::slice(input, 2, tile.start, tile.end);
|
||||||
|
auto output_tile = on_processing(input_tile, tile);
|
||||||
|
if (output_tile.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64_t expected_tile_frames = 1 + (input_tile.shape()[2] - 1) * output_scale;
|
||||||
|
if (output_tile.dim() < 3 || output_tile.shape()[2] != expected_tile_frames) {
|
||||||
|
LOG_ERROR("unexpected temporal tile output shape: expected %lld frames, got %lld",
|
||||||
|
(long long)expected_tile_frames,
|
||||||
|
output_tile.dim() < 3 ? -1LL : (long long)output_tile.shape()[2]);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.empty()) {
|
||||||
|
auto output_shape = output_tile.shape();
|
||||||
|
output_shape[2] = output_frames;
|
||||||
|
output = sd::Tensor<float>::zeros(std::move(output_shape));
|
||||||
|
} else {
|
||||||
|
if (output.dim() != output_tile.dim()) {
|
||||||
|
LOG_ERROR("temporal tile output rank mismatch: expected %lld, got %lld",
|
||||||
|
(long long)output.dim(),
|
||||||
|
(long long)output_tile.dim());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
for (size_t dim = 0; dim < static_cast<size_t>(output.dim()); ++dim) {
|
||||||
|
if (dim != 2 && output.shape()[dim] != output_tile.shape()[dim]) {
|
||||||
|
LOG_ERROR("temporal tile output shape mismatch at dimension %zu", dim);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64_t output_start = tile.start * output_scale;
|
||||||
|
const int64_t inner = output.shape()[0] * output.shape()[1];
|
||||||
|
const int64_t outer = output.numel() / (inner * output.shape()[2]);
|
||||||
|
const int64_t tile_frames = output_tile.shape()[2];
|
||||||
|
for (int64_t frame = 0; frame < tile_frames; ++frame) {
|
||||||
|
float weight = 1.f;
|
||||||
|
if (!tile.first && overlap_frames > 0 && frame < overlap_frames) {
|
||||||
|
weight *= smootherstep(static_cast<float>(frame + 1) /
|
||||||
|
static_cast<float>(overlap_frames + 1));
|
||||||
|
}
|
||||||
|
if (!tile.last && overlap_frames > 0 && frame >= tile_frames - overlap_frames) {
|
||||||
|
weight *= smootherstep(static_cast<float>(tile_frames - frame) /
|
||||||
|
static_cast<float>(overlap_frames + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64_t output_frame = output_start + frame;
|
||||||
|
GGML_ASSERT(output_frame >= 0 && output_frame < output_frames);
|
||||||
|
weights[static_cast<size_t>(output_frame)] += weight;
|
||||||
|
for (int64_t outer_index = 0; outer_index < outer; ++outer_index) {
|
||||||
|
const int64_t src_offset = (outer_index * tile_frames + frame) * inner;
|
||||||
|
const int64_t dst_offset = (outer_index * output_frames + output_frame) * inner;
|
||||||
|
for (int64_t inner_index = 0; inner_index < inner; ++inner_index) {
|
||||||
|
output[dst_offset + inner_index] += output_tile[src_offset + inner_index] * weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const int64_t inner = output.shape()[0] * output.shape()[1];
|
||||||
|
const int64_t outer = output.numel() / (inner * output.shape()[2]);
|
||||||
|
for (int64_t frame = 0; frame < output_frames; ++frame) {
|
||||||
|
const float weight = weights[static_cast<size_t>(frame)];
|
||||||
|
if (weight <= 0.f) {
|
||||||
|
LOG_ERROR("temporal tiling left output frame %lld uncovered", (long long)frame);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
for (int64_t outer_index = 0; outer_index < outer; ++outer_index) {
|
||||||
|
const int64_t offset = (outer_index * output_frames + frame) * inner;
|
||||||
|
for (int64_t inner_index = 0; inner_index < inner; ++inner_index) {
|
||||||
|
output[offset + inner_index] /= weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __SD_MODEL_VAE_VAE_TILING_HPP__
|
||||||
@ -1219,9 +1219,9 @@ namespace WAN {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ggml_tensor* decode_partial(GGMLRunnerContext* ctx,
|
ggml_tensor* decode_tiled_chunk(GGMLRunnerContext* ctx,
|
||||||
ggml_tensor* z,
|
ggml_tensor* z,
|
||||||
int i,
|
int chunk_idx,
|
||||||
int64_t b = 1) {
|
int64_t b = 1) {
|
||||||
// z: [b*c, t, h, w]
|
// z: [b*c, t, h, w]
|
||||||
GGML_ASSERT(b == 1);
|
GGML_ASSERT(b == 1);
|
||||||
@ -1229,14 +1229,30 @@ namespace WAN {
|
|||||||
auto decoder = std::dynamic_pointer_cast<Decoder3d>(blocks["decoder"]);
|
auto decoder = std::dynamic_pointer_cast<Decoder3d>(blocks["decoder"]);
|
||||||
auto conv2 = std::dynamic_pointer_cast<CausalConv3d>(blocks["conv2"]);
|
auto conv2 = std::dynamic_pointer_cast<CausalConv3d>(blocks["conv2"]);
|
||||||
|
|
||||||
auto x = conv2->forward(ctx, z);
|
ggml_tensor* x;
|
||||||
// sd::ggml_graph_cut::mark_graph_cut(x, "wan_vae.decode_partial.prelude", "x");
|
if (is_2D) {
|
||||||
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, i, i + 1); // [b*c, 1, h, w]
|
auto conv2_2d = std::dynamic_pointer_cast<Conv2dBut3d>(blocks["conv2"]);
|
||||||
|
x = conv2_2d->forward(ctx, z);
|
||||||
|
} else {
|
||||||
|
x = conv2->forward(ctx, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
ggml_tensor* out = nullptr;
|
||||||
|
for (int64_t frame = 0; frame < x->ne[2]; ++frame) {
|
||||||
|
const int global_frame = chunk_idx + static_cast<int>(frame);
|
||||||
|
auto in = ggml_ext_slice(ctx->ggml_ctx, x, 2, frame, frame + 1);
|
||||||
_conv_idx = 0;
|
_conv_idx = 0;
|
||||||
auto out = decoder->forward(ctx, in, b, _feat_map, _conv_idx, i);
|
auto out_frame = decoder->forward(ctx, in, b, _feat_map, _conv_idx, global_frame);
|
||||||
out = unpatchify(ctx->ggml_ctx, out, patch_size, b);
|
if (is_2D && global_frame > 0) {
|
||||||
// sd::ggml_graph_cut::mark_graph_cut(out, "wan_vae.decode_partial.final", "out");
|
auto repeated = out_frame;
|
||||||
return out;
|
for (int repeat = 1; repeat < 4; ++repeat) {
|
||||||
|
repeated = ggml_concat(ctx->ggml_ctx, repeated, out_frame, 2);
|
||||||
|
}
|
||||||
|
out_frame = repeated;
|
||||||
|
}
|
||||||
|
out = out == nullptr ? out_frame : ggml_concat(ctx->ggml_ctx, out, out_frame, 2);
|
||||||
|
}
|
||||||
|
return unpatchify(ctx->ggml_ctx, out, patch_size, b);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1272,6 +1288,15 @@ namespace WAN {
|
|||||||
return "wan_vae";
|
return "wan_vae";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool supports_temporal_tiling(VAETemporalDirection direction) const override {
|
||||||
|
return direction == VAETemporalDirection::DECODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_temporal_tile_output_scale(VAETemporalDirection direction) const override {
|
||||||
|
SD_UNUSED(direction);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors) override {
|
||||||
ae.get_param_tensors(tensors, weight_prefix);
|
ae.get_param_tensors(tensors, weight_prefix);
|
||||||
}
|
}
|
||||||
@ -1346,8 +1371,8 @@ namespace WAN {
|
|||||||
return gf;
|
return gf;
|
||||||
}
|
}
|
||||||
|
|
||||||
ggml_cgraph* build_graph_partial(const sd::Tensor<float>& z_tensor, bool decode_graph, int i) {
|
ggml_cgraph* build_temporal_tile_graph(const sd::Tensor<float>& z_tensor, int chunk_idx) {
|
||||||
ggml_cgraph* gf = new_graph_custom(20480);
|
ggml_cgraph* gf = new_graph_custom(std::max<size_t>(20480, 10240 * z_tensor.shape()[2]));
|
||||||
|
|
||||||
ae.clear_cache();
|
ae.clear_cache();
|
||||||
|
|
||||||
@ -1360,7 +1385,7 @@ namespace WAN {
|
|||||||
|
|
||||||
auto runner_ctx = get_context();
|
auto runner_ctx = get_context();
|
||||||
|
|
||||||
ggml_tensor* out = decode_graph ? ae.decode_partial(&runner_ctx, z, i) : ae.encode(&runner_ctx, z);
|
ggml_tensor* out = ae.decode_tiled_chunk(&runner_ctx, z, chunk_idx);
|
||||||
|
|
||||||
for (size_t feat_idx = 0; feat_idx < ae._feat_map.size(); feat_idx++) {
|
for (size_t feat_idx = 0; feat_idx < ae._feat_map.size(); feat_idx++) {
|
||||||
ggml_tensor* feat_cache = ae._feat_map[feat_idx];
|
ggml_tensor* feat_cache = ae._feat_map[feat_idx];
|
||||||
@ -1375,20 +1400,53 @@ namespace WAN {
|
|||||||
return gf;
|
return gf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sd::Tensor<float> _compute_temporal_tiled(const int n_threads,
|
||||||
|
const sd::Tensor<float>& input,
|
||||||
|
VAETemporalDirection direction,
|
||||||
|
const VAETemporalTilingConfig& config) override {
|
||||||
|
GGML_ASSERT(direction == VAETemporalDirection::DECODE);
|
||||||
|
VAETemporalTilingConfig stateful_config = config;
|
||||||
|
stateful_config.overlap = 0;
|
||||||
|
auto plan = make_vae_temporal_tile_plan(input.shape()[2], stateful_config);
|
||||||
|
|
||||||
|
LOG_DEBUG("Wan VAE stateful temporal tiling: tile_frames=%d, total latent frames=%lld, tiles=%d",
|
||||||
|
plan.tile_frames,
|
||||||
|
(long long)input.shape()[2],
|
||||||
|
(int)plan.tiles.size());
|
||||||
|
|
||||||
|
free_cache_ctx_and_buffer();
|
||||||
|
cache_tensor_map.clear();
|
||||||
|
ae.clear_cache();
|
||||||
|
|
||||||
|
auto output = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& input_tile, const VAETemporalTile& tile) {
|
||||||
|
LOG_DEBUG("Wan VAE temporal tile %d/%d: latent frames [%lld, %lld)",
|
||||||
|
tile.index + 1,
|
||||||
|
(int)plan.tiles.size(),
|
||||||
|
(long long)tile.start,
|
||||||
|
(long long)tile.end);
|
||||||
|
auto get_graph = [&]() -> ggml_cgraph* {
|
||||||
|
return build_temporal_tile_graph(input_tile, static_cast<int>(tile.start));
|
||||||
|
};
|
||||||
|
return restore_trailing_singleton_dims(
|
||||||
|
GGMLRunner::compute<float>(get_graph, n_threads, true, true, true),
|
||||||
|
static_cast<size_t>(input.dim()));
|
||||||
|
});
|
||||||
|
|
||||||
|
free_cache_ctx_and_buffer();
|
||||||
|
cache_tensor_map.clear();
|
||||||
|
ae.clear_cache();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
sd::Tensor<float> _compute(const int n_threads,
|
sd::Tensor<float> _compute(const int n_threads,
|
||||||
const sd::Tensor<float>& z,
|
const sd::Tensor<float>& z,
|
||||||
bool decode_graph) override {
|
bool decode_graph) override {
|
||||||
if (true) {
|
|
||||||
sd::Tensor<float> input;
|
sd::Tensor<float> input;
|
||||||
if (z.dim() == 4) {
|
if (z.dim() == 4) {
|
||||||
input = z.unsqueeze(2);
|
input = z.unsqueeze(2);
|
||||||
}
|
}
|
||||||
auto get_graph = [&]() -> ggml_cgraph* {
|
auto get_graph = [&]() -> ggml_cgraph* {
|
||||||
if (input.empty()) {
|
return build_graph(input.empty() ? z : input, decode_graph);
|
||||||
return build_graph(z, decode_graph);
|
|
||||||
} else {
|
|
||||||
return build_graph(input, decode_graph);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
auto result = restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, true, true, true),
|
auto result = restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, true, true, true),
|
||||||
input.empty() ? z.dim() : input.dim());
|
input.empty() ? z.dim() : input.dim());
|
||||||
@ -1396,37 +1454,6 @@ namespace WAN {
|
|||||||
result.squeeze_(2);
|
result.squeeze_(2);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else { // chunk 1 result is weird
|
|
||||||
ae.clear_cache();
|
|
||||||
int64_t t = z.shape()[2];
|
|
||||||
int i = 0;
|
|
||||||
auto get_graph = [&]() -> ggml_cgraph* {
|
|
||||||
return build_graph_partial(z, decode_graph, i);
|
|
||||||
};
|
|
||||||
auto out_opt = GGMLRunner::compute<float>(get_graph, n_threads, true, true, true);
|
|
||||||
if (!out_opt.has_value()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
sd::Tensor<float> out = std::move(*out_opt);
|
|
||||||
ae.clear_cache();
|
|
||||||
if (t == 1) {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
sd::Tensor<float> output = std::move(out);
|
|
||||||
|
|
||||||
for (i = 1; i < t; i++) {
|
|
||||||
auto chunk_opt = GGMLRunner::compute<float>(get_graph, n_threads, true, true, true);
|
|
||||||
if (!chunk_opt.has_value()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
out = std::move(*chunk_opt);
|
|
||||||
ae.clear_cache();
|
|
||||||
output = sd::ops::concat(output, out, 2);
|
|
||||||
}
|
|
||||||
free_cache_ctx_and_buffer();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test() {
|
void test() {
|
||||||
|
|||||||
@ -2378,11 +2378,9 @@ public:
|
|||||||
sd::Tensor<float> vae_latents;
|
sd::Tensor<float> vae_latents;
|
||||||
sd::Tensor<float> decoded;
|
sd::Tensor<float> decoded;
|
||||||
if (preview_vae) {
|
if (preview_vae) {
|
||||||
preview_vae->set_temporal_tiling_enabled(vae_tiling_params.temporal_tiling);
|
|
||||||
vae_latents = preview_vae->diffusion_to_vae_latents(_latents);
|
vae_latents = preview_vae->diffusion_to_vae_latents(_latents);
|
||||||
decoded = preview_vae->decode(n_threads, vae_latents, vae_tiling_params, is_video, circular_x, circular_y, true);
|
decoded = preview_vae->decode(n_threads, vae_latents, vae_tiling_params, is_video, circular_x, circular_y, true);
|
||||||
} else {
|
} else {
|
||||||
first_stage_model->set_temporal_tiling_enabled(vae_tiling_params.temporal_tiling);
|
|
||||||
vae_latents = first_stage_model->diffusion_to_vae_latents(_latents);
|
vae_latents = first_stage_model->diffusion_to_vae_latents(_latents);
|
||||||
decoded = first_stage_model->decode(n_threads, vae_latents, vae_tiling_params, is_video, circular_x, circular_y, true);
|
decoded = first_stage_model->decode(n_threads, vae_latents, vae_tiling_params, is_video, circular_x, circular_y, true);
|
||||||
}
|
}
|
||||||
@ -3085,16 +3083,14 @@ public:
|
|||||||
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);
|
||||||
}
|
}
|
||||||
auto latents = first_stage_model->diffusion_to_vae_latents(x);
|
auto latents = first_stage_model->diffusion_to_vae_latents(x);
|
||||||
first_stage_model->set_temporal_tiling_enabled(vae_tiling_params.temporal_tiling);
|
|
||||||
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);
|
||||||
if (decoded.empty() && auto_fit_enabled) {
|
const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode();
|
||||||
bool prefer_temporal_tiling = decode_video && std::dynamic_pointer_cast<LTXVideoVAE>(first_stage_model) != nullptr;
|
while (decoded.empty() &&
|
||||||
if (sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling)) {
|
auto_fit_enabled &&
|
||||||
|
sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling)) {
|
||||||
first_stage_model->free_compute_buffer();
|
first_stage_model->free_compute_buffer();
|
||||||
first_stage_model->set_temporal_tiling_enabled(vae_tiling_params.temporal_tiling);
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user