Skip to content

Commit 449165c

Browse files
authored
feat: stream LTX VAE temporal tile decoding (leejet#1539)
1 parent adaa599 commit 449165c

1 file changed

Lines changed: 145 additions & 10 deletions

File tree

src/ltx_vae.hpp

Lines changed: 145 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,27 @@ namespace LTXVAE {
11581158
return WAN::WanVAE::unpatchify(ctx->ggml_ctx, out, patch_size, 1);
11591159
}
11601160

1161+
ggml_tensor* decode_tiled_chunk(GGMLRunnerContext* ctx,
1162+
ggml_tensor* z,
1163+
ggml_tensor* timestep,
1164+
std::vector<ggml_tensor*>& feat_map,
1165+
int chunk_idx,
1166+
int temporal_tile_overlap,
1167+
int& feat_idx) {
1168+
auto decoder = std::dynamic_pointer_cast<Decoder>(blocks["decoder"]);
1169+
auto processor = std::dynamic_pointer_cast<PerChannelStatistics>(blocks["per_channel_statistics"]);
1170+
auto latents = processor->un_normalize(ctx, z);
1171+
1172+
feat_idx = 0;
1173+
int chunk_overlap = temporal_tile_overlap; // modified by forward_tiled_frame temporal inflation
1174+
auto out_chunk = decoder->forward_tiled_frame(ctx, latents, timestep,
1175+
feat_map, feat_idx, chunk_idx, chunk_overlap);
1176+
if (chunk_overlap > 0) {
1177+
out_chunk = ggml_ext_slice(ctx->ggml_ctx, out_chunk, 2, 0, out_chunk->ne[2] - chunk_overlap);
1178+
}
1179+
return WAN::WanVAE::unpatchify(ctx->ggml_ctx, out_chunk, patch_size, 1);
1180+
}
1181+
11611182
ggml_tensor* encode(GGMLRunnerContext* ctx,
11621183
ggml_tensor* x) {
11631184
GGML_ASSERT(!decode_only);
@@ -1296,6 +1317,41 @@ struct LTXVideoVAE : public VAE {
12961317
vae.get_param_tensors(tensors, prefix);
12971318
}
12981319

1320+
struct TemporalTilePlan {
1321+
int frames = 1;
1322+
int overlap = 0;
1323+
int stride = 1;
1324+
int num_tiles = 1;
1325+
};
1326+
1327+
TemporalTilePlan resolve_temporal_tile_plan(int64_t total_frames) const {
1328+
TemporalTilePlan plan;
1329+
plan.frames = std::max(1, temporal_tile_frames);
1330+
plan.overlap = std::max(0, temporal_tile_overlap);
1331+
1332+
if (plan.overlap >= plan.frames) {
1333+
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to temporal_tile_frames (%d), adjusting values to avoid empty decode windows",
1334+
plan.overlap,
1335+
plan.frames);
1336+
plan.overlap = plan.frames - 1;
1337+
}
1338+
if (total_frames > 1 && plan.overlap >= total_frames) {
1339+
LOG_WARN("temporal_tile_overlap (%d) is greater than or equal to total latent frames (%lld), adjusting values to decode at least one tile",
1340+
plan.overlap,
1341+
(long long)total_frames);
1342+
plan.overlap = static_cast<int>(total_frames - 1);
1343+
}
1344+
1345+
plan.stride = std::max(1, plan.frames - plan.overlap);
1346+
int64_t tiled_frames = std::max<int64_t>(1, total_frames - plan.overlap);
1347+
plan.num_tiles = total_frames > 0 ? static_cast<int>((tiled_frames + plan.stride - 1) / plan.stride) : 0;
1348+
return plan;
1349+
}
1350+
1351+
std::string temporal_feat_cache_name(size_t feat_idx) const {
1352+
return "ltx_vae_temporal_feat:" + std::to_string(feat_idx);
1353+
}
1354+
12991355
ggml_cgraph* build_graph(const sd::Tensor<float>& z_tensor, bool decode_graph) {
13001356
ggml_cgraph* gf = new_graph_custom(20480);
13011357
ggml_tensor* z = make_input(z_tensor);
@@ -1306,21 +1362,97 @@ struct LTXVideoVAE : public VAE {
13061362

13071363
auto runner_ctx = get_context();
13081364
ggml_tensor* out;
1309-
bool use_tiled = decode_graph && temporal_tiling_enabled &&
1310-
z_tensor.dim() == 5 && z_tensor.shape()[2] > 1;
1311-
if (use_tiled) {
1312-
LOG_DEBUG("Using LTX VAE temporal tiling params: temporal_tile_frames=%d, temporal_tile_overlap=%d",
1313-
temporal_tile_frames,
1314-
temporal_tile_overlap);
1315-
out = vae.decode_tiled(&runner_ctx, z, timestep, temporal_tile_frames, temporal_tile_overlap);
1316-
} else {
1317-
out = decode_graph ? vae.decode(&runner_ctx, z, timestep) : vae.encode(&runner_ctx, z);
1318-
}
1365+
out = decode_graph ? vae.decode(&runner_ctx, z, timestep) : vae.encode(&runner_ctx, z);
13191366
ggml_build_forward_expand(gf, out);
13201367

13211368
return gf;
13221369
}
13231370

1371+
ggml_cgraph* build_temporal_tile_graph(const sd::Tensor<float>& z_chunk_tensor,
1372+
int chunk_idx,
1373+
int chunk_overlap) {
1374+
ggml_cgraph* gf = new_graph_custom(20480);
1375+
ggml_tensor* z = make_input(z_chunk_tensor);
1376+
ggml_tensor* timestep = nullptr;
1377+
if (timestep_conditioning) {
1378+
timestep = make_input(decode_timestep_tensor);
1379+
}
1380+
1381+
std::vector<ggml_tensor*> feat_map(128, nullptr);
1382+
for (size_t feat_idx = 0; feat_idx < feat_map.size(); ++feat_idx) {
1383+
feat_map[feat_idx] = get_cache_tensor_by_name(temporal_feat_cache_name(feat_idx));
1384+
}
1385+
1386+
auto runner_ctx = get_context();
1387+
int feat_count = 0;
1388+
ggml_tensor* out = vae.decode_tiled_chunk(&runner_ctx,
1389+
z,
1390+
timestep,
1391+
feat_map,
1392+
chunk_idx,
1393+
chunk_overlap,
1394+
feat_count);
1395+
1396+
for (int feat_idx = 0; feat_idx < feat_count && feat_idx < static_cast<int>(feat_map.size()); ++feat_idx) {
1397+
ggml_tensor* feat_cache = feat_map[static_cast<size_t>(feat_idx)];
1398+
if (feat_cache != nullptr) {
1399+
cache(temporal_feat_cache_name(static_cast<size_t>(feat_idx)), feat_cache);
1400+
ggml_build_forward_expand(gf, feat_cache);
1401+
}
1402+
}
1403+
1404+
ggml_build_forward_expand(gf, out);
1405+
return gf;
1406+
}
1407+
1408+
sd::Tensor<float> decode_temporal_tiled_streaming(const int n_threads,
1409+
const sd::Tensor<float>& input,
1410+
size_t expected_dim) {
1411+
const int64_t total_frames = input.shape()[2];
1412+
TemporalTilePlan plan = resolve_temporal_tile_plan(total_frames);
1413+
1414+
LOG_DEBUG("Using streaming temporal tiling: temporal_tile_frames=%d, temporal_tile_overlap=%d, total latent frames=%lld, resulting in %d tiles",
1415+
plan.frames,
1416+
plan.overlap,
1417+
(long long)total_frames,
1418+
plan.num_tiles);
1419+
1420+
free_cache_ctx_and_buffer();
1421+
cache_tensor_map.clear();
1422+
1423+
sd::Tensor<float> output;
1424+
for (int64_t start = 0; start < total_frames - plan.overlap; start += plan.stride) {
1425+
const int64_t end = std::min<int64_t>(total_frames, start + plan.frames);
1426+
const int chunk_overlap = end < total_frames ? plan.overlap : 0;
1427+
auto z_chunk = sd::ops::slice(input, 2, start, end);
1428+
1429+
LOG_DEBUG("LTX VAE temporal tile %lld/%d: latent frames [%lld, %lld), overlap=%d",
1430+
(long long)(start / plan.stride + 1),
1431+
plan.num_tiles,
1432+
(long long)start,
1433+
(long long)end,
1434+
chunk_overlap);
1435+
1436+
auto get_graph = [&]() -> ggml_cgraph* {
1437+
return build_temporal_tile_graph(z_chunk,
1438+
static_cast<int>(start),
1439+
chunk_overlap);
1440+
};
1441+
auto chunk = restore_trailing_singleton_dims(GGMLRunner::compute<float>(get_graph, n_threads, true),
1442+
expected_dim);
1443+
if (chunk.empty()) {
1444+
free_cache_ctx_and_buffer();
1445+
cache_tensor_map.clear();
1446+
return {};
1447+
}
1448+
output = output.empty() ? std::move(chunk) : sd::ops::concat(output, chunk, 2);
1449+
}
1450+
1451+
free_cache_ctx_and_buffer();
1452+
cache_tensor_map.clear();
1453+
return output;
1454+
}
1455+
13241456
ggml_cgraph* build_latent_statistics_graph(const sd::Tensor<float>& z_tensor, bool normalize) {
13251457
ggml_cgraph* gf = new_graph_custom(1024);
13261458
ggml_tensor* z = make_input(z_tensor);
@@ -1356,6 +1488,9 @@ struct LTXVideoVAE : public VAE {
13561488
input = sd::ops::slice(input, 2, 0, cropped_t);
13571489
}
13581490
}
1491+
if (decode_graph && temporal_tiling_enabled && input.dim() == 5 && input.shape()[2] > 1) {
1492+
return decode_temporal_tiled_streaming(n_threads, input, expected_dim);
1493+
}
13591494
auto get_graph = [&]() -> ggml_cgraph* {
13601495
return build_graph(input, decode_graph);
13611496
};

0 commit comments

Comments
 (0)