94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
811 lines
31 KiB
Python
811 lines
31 KiB
Python
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from abc import ABC, abstractmethod
|
|
from functools import lru_cache
|
|
from math import isqrt, prod
|
|
from typing import Optional, cast
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torch.distributed as dist
|
|
from diffusers.models.autoencoders.vae import DiagonalGaussianDistribution
|
|
from diffusers.utils.torch_utils import randn_tensor
|
|
from torch import nn
|
|
|
|
from sglang.multimodal_gen.configs.models import VAEConfig
|
|
from sglang.multimodal_gen.configs.models.vaes.base import (
|
|
should_use_spatial_shard_parallel_decode,
|
|
)
|
|
from sglang.multimodal_gen.runtime.distributed import (
|
|
get_decode_parallel_group_coordinator,
|
|
get_decode_parallel_world_size,
|
|
get_sp_parallel_rank,
|
|
get_sp_world_size,
|
|
model_parallel_is_initialized,
|
|
)
|
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
|
LayerwiseOffloadableModuleMixin,
|
|
)
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def _cached_decode_parallel_world_size(
|
|
is_dist_initialized: bool, is_model_parallel_initialized: bool, group_id: int
|
|
) -> int:
|
|
if not is_dist_initialized or not is_model_parallel_initialized:
|
|
return 1
|
|
return get_decode_parallel_world_size()
|
|
|
|
|
|
def _decode_parallel_world_size() -> int:
|
|
is_dist_initialized = dist.is_initialized()
|
|
is_model_parallel_initialized = model_parallel_is_initialized()
|
|
if not is_dist_initialized or not is_model_parallel_initialized:
|
|
return _cached_decode_parallel_world_size(
|
|
is_dist_initialized, is_model_parallel_initialized, 0
|
|
)
|
|
return _cached_decode_parallel_world_size(
|
|
is_dist_initialized,
|
|
is_model_parallel_initialized,
|
|
id(get_decode_parallel_group_coordinator()),
|
|
)
|
|
|
|
|
|
def has_decode_parallel_world() -> bool:
|
|
return _decode_parallel_world_size() > 1
|
|
|
|
|
|
def can_install_spatial_shard_parallel_decode(config: VAEConfig | None) -> bool:
|
|
world_size = _decode_parallel_world_size()
|
|
return (
|
|
config is not None
|
|
and world_size > 1
|
|
and should_use_spatial_shard_parallel_decode(config, world_size=world_size)
|
|
)
|
|
|
|
|
|
def should_run_spatial_shard_parallel_decode(
|
|
config: VAEConfig, z: torch.Tensor
|
|
) -> bool:
|
|
world_size = _decode_parallel_world_size()
|
|
return world_size > 1 and should_use_spatial_shard_parallel_decode(
|
|
config, z, world_size
|
|
)
|
|
|
|
|
|
class ParallelTiledVAE(ABC, nn.Module, LayerwiseOffloadableModuleMixin):
|
|
layerwise_offload_dit_group_enabled = False
|
|
layer_names = [
|
|
"encoder.down_blocks",
|
|
"decoder.up_blocks",
|
|
"encoder.down",
|
|
"decoder.up",
|
|
]
|
|
tile_sample_min_height: int
|
|
tile_sample_min_width: int
|
|
tile_sample_min_num_frames: int
|
|
tile_sample_stride_height: int
|
|
tile_sample_stride_width: int
|
|
tile_sample_stride_num_frames: int
|
|
blend_num_frames: int
|
|
use_tiling: bool
|
|
use_temporal_tiling: bool
|
|
use_parallel_tiling: bool
|
|
use_parallel_decode: bool
|
|
parallel_decode_mode: str
|
|
|
|
def __init__(self, config: VAEConfig, **kwargs) -> None:
|
|
super().__init__()
|
|
self.config = config
|
|
self.tile_sample_min_height = config.tile_sample_min_height
|
|
self.tile_sample_min_width = config.tile_sample_min_width
|
|
self.tile_sample_min_num_frames = config.tile_sample_min_num_frames
|
|
self.tile_sample_stride_height = config.tile_sample_stride_height
|
|
self.tile_sample_stride_width = config.tile_sample_stride_width
|
|
self.tile_sample_stride_num_frames = config.tile_sample_stride_num_frames
|
|
self.blend_num_frames = config.blend_num_frames
|
|
self.use_tiling = config.use_tiling
|
|
self.use_temporal_tiling = config.use_temporal_tiling
|
|
self.use_parallel_tiling = config.use_parallel_tiling
|
|
self.use_parallel_decode = config.use_parallel_decode
|
|
self.parallel_decode_mode = config.parallel_decode_mode
|
|
|
|
@property
|
|
def device(self):
|
|
return next(self.parameters()).device
|
|
|
|
@property
|
|
def temporal_compression_ratio(self) -> int:
|
|
return cast(int, self.config.temporal_compression_ratio)
|
|
|
|
@property
|
|
def spatial_compression_ratio(self) -> int:
|
|
return cast(int, self.config.spatial_compression_ratio)
|
|
|
|
@property
|
|
def scaling_factor(self) -> float | torch.Tensor:
|
|
return cast(float | torch.Tensor, self.config.scaling_factor)
|
|
|
|
@abstractmethod
|
|
def _encode(self, *args, **kwargs) -> torch.Tensor:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def _decode(self, *args, **kwargs) -> torch.Tensor:
|
|
pass
|
|
|
|
def encode(self, x: torch.Tensor) -> DiagonalGaussianDistribution:
|
|
batch_size, num_channels, num_frames, height, width = x.shape
|
|
latent_num_frames = (num_frames - 1) // self.temporal_compression_ratio + 1
|
|
|
|
if (
|
|
self.use_tiling
|
|
and self.use_temporal_tiling
|
|
and num_frames > self.tile_sample_min_num_frames
|
|
):
|
|
latents = self.tiled_encode(x)[:, :, :latent_num_frames]
|
|
elif self.use_tiling and (
|
|
width > self.tile_sample_min_width or height > self.tile_sample_min_height
|
|
):
|
|
latents = self.spatial_tiled_encode(x)[:, :, :latent_num_frames]
|
|
else:
|
|
latents = self._encode(x)[:, :, :latent_num_frames]
|
|
return DiagonalGaussianDistribution(latents)
|
|
|
|
def decode(self, z: torch.Tensor) -> torch.Tensor:
|
|
batch_size, num_channels, num_frames, height, width = z.shape
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_stride_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_num_frames = (
|
|
self.tile_sample_min_num_frames // self.temporal_compression_ratio
|
|
)
|
|
num_sample_frames = (num_frames - 1) * self.temporal_compression_ratio + 1
|
|
|
|
if should_run_spatial_shard_parallel_decode(self.config, z):
|
|
return self._decode(z)[:, :, :num_sample_frames]
|
|
|
|
if (
|
|
self.parallel_decode_mode == "tiled"
|
|
and self.use_tiling
|
|
and self.use_parallel_tiling
|
|
and get_sp_world_size() > 1
|
|
):
|
|
return self.parallel_tiled_decode(z)[:, :, :num_sample_frames]
|
|
if (
|
|
self.use_tiling
|
|
and self.use_temporal_tiling
|
|
and num_frames > tile_latent_min_num_frames
|
|
):
|
|
return self.tiled_decode(z)[:, :, :num_sample_frames]
|
|
|
|
if self.use_tiling and (
|
|
width > tile_latent_min_width or height > tile_latent_min_height
|
|
):
|
|
return self.spatial_tiled_decode(z)[:, :, :num_sample_frames]
|
|
|
|
return self._decode(z)[:, :, :num_sample_frames]
|
|
|
|
def blend_v(
|
|
self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
|
|
) -> torch.Tensor:
|
|
blend_extent = min(a.shape[-2], b.shape[-2], blend_extent)
|
|
for y in range(blend_extent):
|
|
b[:, :, :, y, :] = a[:, :, :, -blend_extent + y, :] * (
|
|
1 - y / blend_extent
|
|
) + b[:, :, :, y, :] * (y / blend_extent)
|
|
return b
|
|
|
|
def blend_h(
|
|
self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
|
|
) -> torch.Tensor:
|
|
blend_extent = min(a.shape[-1], b.shape[-1], blend_extent)
|
|
for x in range(blend_extent):
|
|
b[:, :, :, :, x] = a[:, :, :, :, -blend_extent + x] * (
|
|
1 - x / blend_extent
|
|
) + b[:, :, :, :, x] * (x / blend_extent)
|
|
return b
|
|
|
|
def blend_t(
|
|
self, a: torch.Tensor, b: torch.Tensor, blend_extent: int
|
|
) -> torch.Tensor:
|
|
blend_extent = min(a.shape[-3], b.shape[-3], blend_extent)
|
|
for x in range(blend_extent):
|
|
b[:, :, x, :, :] = a[:, :, -blend_extent + x, :, :] * (
|
|
1 - x / blend_extent
|
|
) + b[:, :, x, :, :] * (x / blend_extent)
|
|
return b
|
|
|
|
def spatial_tiled_encode(self, x: torch.Tensor) -> torch.Tensor:
|
|
r"""Encode a batch of images using a tiled encoder.
|
|
|
|
Args:
|
|
x (`torch.Tensor`): Input batch of videos.
|
|
|
|
Returns:
|
|
`torch.Tensor`:
|
|
The latent representation of the encoded videos.
|
|
"""
|
|
_, _, _, height, width = x.shape
|
|
# latent_height = height // self.spatial_compression_ratio
|
|
# latent_width = width // self.spatial_compression_ratio
|
|
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_min_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_height = (
|
|
self.tile_sample_stride_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_width = (
|
|
self.tile_sample_stride_width // self.spatial_compression_ratio
|
|
)
|
|
|
|
blend_height = tile_latent_min_height - tile_latent_stride_height
|
|
blend_width = tile_latent_min_width - tile_latent_stride_width
|
|
|
|
# Split x into overlapping tiles and encode them separately.
|
|
# The tiles have an overlap to avoid seams between tiles.
|
|
rows = []
|
|
for i in range(0, height, self.tile_sample_stride_height):
|
|
row = []
|
|
for j in range(0, width, self.tile_sample_stride_width):
|
|
tile = x[
|
|
:,
|
|
:,
|
|
:,
|
|
i : i + self.tile_sample_min_height,
|
|
j : j + self.tile_sample_min_width,
|
|
]
|
|
tile = self._encode(tile)
|
|
row.append(tile)
|
|
rows.append(row)
|
|
|
|
return self._merge_spatial_tiles(
|
|
rows,
|
|
blend_height,
|
|
blend_width,
|
|
tile_latent_stride_height,
|
|
tile_latent_stride_width,
|
|
)
|
|
|
|
def parallel_tiled_decode(self, z: torch.FloatTensor) -> torch.FloatTensor:
|
|
"""
|
|
Parallel version of tiled_decode that distributes both temporal and spatial computation across GPUs
|
|
"""
|
|
world_size, rank = get_sp_world_size(), get_sp_parallel_rank()
|
|
_, _, T, H, W = z.shape
|
|
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_min_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_num_frames = (
|
|
self.tile_sample_min_num_frames // self.temporal_compression_ratio
|
|
)
|
|
tile_latent_stride_height = (
|
|
self.tile_sample_stride_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_width = (
|
|
self.tile_sample_stride_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_num_frames = (
|
|
self.tile_sample_stride_num_frames // self.temporal_compression_ratio
|
|
)
|
|
|
|
blend_height = self.tile_sample_min_height - self.tile_sample_stride_height
|
|
blend_width = self.tile_sample_min_width - self.tile_sample_stride_width
|
|
|
|
# Calculate tile dimensions
|
|
num_t_tiles = (
|
|
T + tile_latent_stride_num_frames - 1
|
|
) // tile_latent_stride_num_frames
|
|
num_h_tiles = (H + tile_latent_stride_height - 1) // tile_latent_stride_height
|
|
num_w_tiles = (W + tile_latent_stride_width - 1) // tile_latent_stride_width
|
|
total_spatial_tiles = num_h_tiles * num_w_tiles
|
|
total_tiles = num_t_tiles * total_spatial_tiles
|
|
tiles_per_rank = (total_tiles + world_size - 1) // world_size
|
|
start_tile_idx = rank * tiles_per_rank
|
|
end_tile_idx = min((rank + 1) * tiles_per_rank, total_tiles)
|
|
|
|
local_results = []
|
|
local_dim_metadata = []
|
|
for global_idx in range(start_tile_idx, end_tile_idx):
|
|
t_idx = global_idx // total_spatial_tiles
|
|
spatial_idx = global_idx % total_spatial_tiles
|
|
h_idx = spatial_idx // num_w_tiles
|
|
w_idx = spatial_idx % num_w_tiles
|
|
|
|
t_start = t_idx * tile_latent_stride_num_frames
|
|
h_start = h_idx * tile_latent_stride_height
|
|
w_start = w_idx * tile_latent_stride_width
|
|
|
|
tile = z[
|
|
:,
|
|
:,
|
|
t_start : t_start + tile_latent_min_num_frames + 1,
|
|
h_start : h_start + tile_latent_min_height,
|
|
w_start : w_start + tile_latent_min_width,
|
|
]
|
|
decoded_tile = self._decode(tile)
|
|
if t_start > 0:
|
|
decoded_tile = decoded_tile[:, :, 1:, :, :]
|
|
local_results.append(decoded_tile.reshape(-1))
|
|
local_dim_metadata.append(decoded_tile.shape)
|
|
|
|
if local_results:
|
|
results = torch.cat(local_results, dim=0).contiguous()
|
|
else:
|
|
results = z.new_empty((0,), dtype=z.dtype)
|
|
del local_results
|
|
|
|
local_size = torch.tensor(
|
|
[results.size(0)], device=results.device, dtype=torch.int64
|
|
)
|
|
all_sizes = [
|
|
torch.zeros(1, device=results.device, dtype=torch.int64)
|
|
for _ in range(world_size)
|
|
]
|
|
dist.all_gather(all_sizes, local_size)
|
|
max_size = max(size.item() for size in all_sizes)
|
|
|
|
padded_results = torch.zeros(
|
|
max_size, device=results.device, dtype=results.dtype
|
|
)
|
|
padded_results[: results.size(0)] = results
|
|
|
|
gathered_dim_metadata = [None] * world_size
|
|
gathered_results = (
|
|
torch.zeros_like(padded_results)
|
|
.repeat(world_size, *[1] * len(padded_results.shape))
|
|
.contiguous()
|
|
)
|
|
dist.all_gather_into_tensor(gathered_results, padded_results)
|
|
dist.all_gather_object(gathered_dim_metadata, local_dim_metadata)
|
|
gathered_dim_metadata = cast(list[list[torch.Size]], gathered_dim_metadata)
|
|
|
|
data: list = [
|
|
[[[] for _ in range(num_w_tiles)] for _ in range(num_h_tiles)]
|
|
for _ in range(num_t_tiles)
|
|
]
|
|
global_idx = 0
|
|
for i, per_rank_metadata in enumerate(gathered_dim_metadata):
|
|
start_shape = 0
|
|
for shape in per_rank_metadata:
|
|
mul_shape = prod(shape)
|
|
current_data = gathered_results[
|
|
i, start_shape : start_shape + mul_shape
|
|
].reshape(shape)
|
|
t_idx = global_idx // total_spatial_tiles
|
|
spatial_idx = global_idx % total_spatial_tiles
|
|
h_idx = spatial_idx // num_w_tiles
|
|
w_idx = spatial_idx % num_w_tiles
|
|
data[t_idx][h_idx][w_idx] = current_data
|
|
start_shape += mul_shape
|
|
global_idx += 1
|
|
|
|
result_slices = []
|
|
last_slice_data = None
|
|
for i, tem_data in enumerate(data):
|
|
slice_data = self._merge_spatial_tiles(
|
|
tem_data,
|
|
blend_height,
|
|
blend_width,
|
|
self.tile_sample_stride_height,
|
|
self.tile_sample_stride_width,
|
|
)
|
|
if i > 0:
|
|
slice_data = self.blend_t(
|
|
last_slice_data, slice_data, self.blend_num_frames
|
|
)
|
|
result_slices.append(
|
|
slice_data[:, :, : self.tile_sample_stride_num_frames, :, :]
|
|
)
|
|
else:
|
|
result_slices.append(
|
|
slice_data[:, :, : self.tile_sample_stride_num_frames + 1, :, :]
|
|
)
|
|
last_slice_data = slice_data
|
|
return torch.cat(result_slices, dim=2)
|
|
|
|
def parallel_patch_decode(self, z: torch.FloatTensor) -> torch.FloatTensor:
|
|
world_size, rank = get_sp_world_size(), get_sp_parallel_rank()
|
|
if world_size <= 1:
|
|
return self._decode(z)
|
|
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_min_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_height = (
|
|
self.tile_sample_stride_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_width = (
|
|
self.tile_sample_stride_width // self.spatial_compression_ratio
|
|
)
|
|
overlap_h = max(0, tile_latent_min_height - tile_latent_stride_height)
|
|
overlap_w = max(0, tile_latent_min_width - tile_latent_stride_width)
|
|
halo_h = overlap_h // 2
|
|
halo_w = overlap_w // 2
|
|
|
|
_, _, _, latent_h, latent_w = z.shape
|
|
scale = self.spatial_compression_ratio
|
|
out_h = latent_h * scale
|
|
out_w = latent_w * scale
|
|
root = isqrt(world_size)
|
|
grid_rows, grid_cols = 1, world_size
|
|
for rows in range(root, 0, -1):
|
|
if world_size % rows == 0:
|
|
grid_rows, grid_cols = rows, world_size // rows
|
|
break
|
|
patch_id = rank
|
|
patch_row = patch_id // grid_cols
|
|
patch_col = patch_id % grid_cols
|
|
|
|
h0 = (patch_row * latent_h) // grid_rows
|
|
h1 = ((patch_row + 1) * latent_h) // grid_rows
|
|
w0 = (patch_col * latent_w) // grid_cols
|
|
w1 = ((patch_col + 1) * latent_w) // grid_cols
|
|
|
|
ext_h0 = max(0, h0 - halo_h)
|
|
ext_h1 = min(latent_h, h1 + halo_h)
|
|
ext_w0 = max(0, w0 - halo_w)
|
|
ext_w1 = min(latent_w, w1 + halo_w)
|
|
|
|
local_patch = z[:, :, :, ext_h0:ext_h1, ext_w0:ext_w1]
|
|
decoded_patch = self._decode(local_patch)
|
|
|
|
crop_top = (h0 - ext_h0) * scale
|
|
crop_bottom = crop_top + (h1 - h0) * scale
|
|
crop_left = (w0 - ext_w0) * scale
|
|
crop_right = crop_left + (w1 - w0) * scale
|
|
decoded_core = decoded_patch[
|
|
:, :, :, crop_top:crop_bottom, crop_left:crop_right
|
|
].contiguous()
|
|
|
|
local_result = decoded_core.reshape(-1)
|
|
local_dim_metadata = torch.tensor(
|
|
decoded_core.shape, device=z.device, dtype=torch.int64
|
|
)
|
|
local_position = torch.tensor(
|
|
[h0 * scale, h1 * scale, w0 * scale, w1 * scale],
|
|
device=z.device,
|
|
dtype=torch.int64,
|
|
)
|
|
gathered_positions = [
|
|
torch.empty_like(local_position) for _ in range(world_size)
|
|
]
|
|
dist.all_gather(gathered_positions, local_position)
|
|
|
|
local_size = torch.tensor(
|
|
[local_result.size(0)], device=z.device, dtype=torch.int64
|
|
)
|
|
gathered_dim_metadata = [
|
|
torch.empty_like(local_dim_metadata) for _ in range(world_size)
|
|
]
|
|
dist.all_gather(gathered_dim_metadata, local_dim_metadata)
|
|
|
|
all_sizes = [
|
|
torch.zeros(1, device=z.device, dtype=torch.int64)
|
|
for _ in range(world_size)
|
|
]
|
|
dist.all_gather(all_sizes, local_size)
|
|
max_size = max(size.item() for size in all_sizes)
|
|
|
|
padded_results = torch.zeros(max_size, device=z.device, dtype=z.dtype)
|
|
padded_results[: local_result.size(0)] = local_result
|
|
gathered_results = torch.empty(
|
|
(world_size, *padded_results.shape),
|
|
device=padded_results.device,
|
|
dtype=padded_results.dtype,
|
|
)
|
|
dist.all_gather_into_tensor(gathered_results, padded_results)
|
|
|
|
dec = z.new_empty(
|
|
(
|
|
decoded_core.shape[0],
|
|
decoded_core.shape[1],
|
|
decoded_core.shape[2],
|
|
out_h,
|
|
out_w,
|
|
)
|
|
)
|
|
for src_rank, positions in enumerate(gathered_positions):
|
|
h_start, h_end, w_start, w_end = [int(x.item()) for x in positions]
|
|
shape = tuple(int(x.item()) for x in gathered_dim_metadata[src_rank])
|
|
patch = gathered_results[src_rank][: prod(shape)].reshape(shape)
|
|
dec[:, :, :, h_start:h_end, w_start:w_end] = patch
|
|
return dec
|
|
|
|
def _merge_spatial_tiles(
|
|
self, tiles, blend_height, blend_width, stride_height, stride_width
|
|
) -> torch.Tensor:
|
|
"""Helper function to merge spatial tiles with blending"""
|
|
result_rows = []
|
|
for i, row in enumerate(tiles):
|
|
result_row = []
|
|
for j, tile in enumerate(row):
|
|
if i > 0:
|
|
tile = self.blend_v(tiles[i - 1][j], tile, blend_height)
|
|
if j > 0:
|
|
tile = self.blend_h(row[j - 1], tile, blend_width)
|
|
result_row.append(tile[:, :, :, :stride_height, :stride_width])
|
|
result_rows.append(torch.cat(result_row, dim=-1))
|
|
return torch.cat(result_rows, dim=-2)
|
|
|
|
def spatial_tiled_decode(self, z: torch.Tensor) -> torch.Tensor:
|
|
r"""
|
|
Decode a batch of images using a tiled decoder.
|
|
|
|
Args:
|
|
z (`torch.Tensor`): Input batch of latent vectors.
|
|
|
|
Returns:
|
|
`torch.Tensor`:
|
|
The decoded images.
|
|
"""
|
|
|
|
_, _, _, height, width = z.shape
|
|
# sample_height = height * self.spatial_compression_ratio
|
|
# sample_width = width * self.spatial_compression_ratio
|
|
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_min_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_height = (
|
|
self.tile_sample_stride_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_stride_width = (
|
|
self.tile_sample_stride_width // self.spatial_compression_ratio
|
|
)
|
|
|
|
blend_height = self.tile_sample_min_height - self.tile_sample_stride_height
|
|
blend_width = self.tile_sample_min_width - self.tile_sample_stride_width
|
|
|
|
# Split z into overlapping tiles and decode them separately.
|
|
# The tiles have an overlap to avoid seams between tiles.
|
|
rows = []
|
|
for i in range(0, height, tile_latent_stride_height):
|
|
row = []
|
|
for j in range(0, width, tile_latent_stride_width):
|
|
tile = z[
|
|
:,
|
|
:,
|
|
:,
|
|
i : i + tile_latent_min_height,
|
|
j : j + tile_latent_min_width,
|
|
]
|
|
decoded = self._decode(tile)
|
|
row.append(decoded)
|
|
rows.append(row)
|
|
return self._merge_spatial_tiles(
|
|
rows,
|
|
blend_height,
|
|
blend_width,
|
|
self.tile_sample_stride_height,
|
|
self.tile_sample_stride_width,
|
|
)
|
|
|
|
def tiled_encode(self, x: torch.Tensor) -> torch.Tensor:
|
|
_, _, num_frames, height, width = x.shape
|
|
|
|
# tile_latent_min_num_frames = self.tile_sample_min_num_frames // self.temporal_compression_ratio
|
|
tile_latent_stride_num_frames = (
|
|
self.tile_sample_stride_num_frames // self.temporal_compression_ratio
|
|
)
|
|
|
|
row = []
|
|
for i in range(0, num_frames, self.tile_sample_stride_num_frames):
|
|
tile = x[:, :, i : i + self.tile_sample_min_num_frames + 1, :, :]
|
|
if self.use_tiling and (
|
|
height > self.tile_sample_min_height
|
|
or width > self.tile_sample_min_width
|
|
):
|
|
tile = self.spatial_tiled_encode(tile)
|
|
else:
|
|
tile = self._encode(tile)
|
|
if i > 0:
|
|
tile = tile[:, :, 1:, :, :]
|
|
row.append(tile)
|
|
result_row = []
|
|
for i, tile in enumerate(row):
|
|
if i > 0:
|
|
tile = self.blend_t(row[i - 1], tile, self.blend_num_frames)
|
|
result_row.append(tile[:, :, :tile_latent_stride_num_frames, :, :])
|
|
else:
|
|
result_row.append(tile[:, :, : tile_latent_stride_num_frames + 1, :, :])
|
|
enc = torch.cat(result_row, dim=2)
|
|
return enc
|
|
|
|
def tiled_decode(self, z: torch.Tensor) -> torch.Tensor:
|
|
batch_size, num_channels, num_frames, height, width = z.shape
|
|
|
|
tile_latent_min_height = (
|
|
self.tile_sample_min_height // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_width = (
|
|
self.tile_sample_min_width // self.spatial_compression_ratio
|
|
)
|
|
tile_latent_min_num_frames = (
|
|
self.tile_sample_min_num_frames // self.temporal_compression_ratio
|
|
)
|
|
tile_latent_stride_num_frames = (
|
|
self.tile_sample_stride_num_frames // self.temporal_compression_ratio
|
|
)
|
|
|
|
row = []
|
|
for i in range(0, num_frames, tile_latent_stride_num_frames):
|
|
tile = z[:, :, i : i + tile_latent_min_num_frames + 1, :, :]
|
|
if self.use_tiling and (
|
|
tile.shape[-1] > tile_latent_min_width
|
|
or tile.shape[-2] > tile_latent_min_height
|
|
):
|
|
decoded = self.spatial_tiled_decode(tile)
|
|
else:
|
|
decoded = self._decode(tile)
|
|
if i > 0:
|
|
decoded = decoded[:, :, 1:, :, :]
|
|
row.append(decoded)
|
|
result_row = []
|
|
for i, tile in enumerate(row):
|
|
if i > 0:
|
|
tile = self.blend_t(row[i - 1], tile, self.blend_num_frames)
|
|
result_row.append(
|
|
tile[:, :, : self.tile_sample_stride_num_frames, :, :]
|
|
)
|
|
else:
|
|
result_row.append(
|
|
tile[:, :, : self.tile_sample_stride_num_frames + 1, :, :]
|
|
)
|
|
|
|
dec = torch.cat(result_row, dim=2)
|
|
return dec
|
|
|
|
def enable_tiling(
|
|
self,
|
|
tile_sample_min_height: int | None = None,
|
|
tile_sample_min_width: int | None = None,
|
|
tile_sample_min_num_frames: int | None = None,
|
|
tile_sample_stride_height: int | None = None,
|
|
tile_sample_stride_width: int | None = None,
|
|
tile_sample_stride_num_frames: int | None = None,
|
|
blend_num_frames: int | None = None,
|
|
use_tiling: bool | None = None,
|
|
use_temporal_tiling: bool | None = None,
|
|
use_parallel_tiling: bool | None = None,
|
|
) -> None:
|
|
r"""
|
|
Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
|
|
compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
|
|
processing larger images.
|
|
|
|
Args:
|
|
tile_sample_min_height (`int`, *optional*):
|
|
The minimum height required for a sample to be separated into tiles across the height dimension.
|
|
tile_sample_min_width (`int`, *optional*):
|
|
The minimum width required for a sample to be separated into tiles across the width dimension.
|
|
tile_sample_min_num_frames (`int`, *optional*):
|
|
The minimum number of frames required for a sample to be separated into tiles across the frame
|
|
dimension.
|
|
tile_sample_stride_height (`int`, *optional*):
|
|
The minimum amount of overlap between two consecutive vertical tiles. This is to ensure that there are
|
|
no tiling artifacts produced across the height dimension.
|
|
tile_sample_stride_width (`int`, *optional*):
|
|
The stride between two consecutive horizontal tiles. This is to ensure that there are no tiling
|
|
artifacts produced across the width dimension.
|
|
tile_sample_stride_num_frames (`int`, *optional*):
|
|
The stride between two consecutive frame tiles. This is to ensure that there are no tiling artifacts
|
|
produced across the frame dimension.
|
|
"""
|
|
self.use_tiling = True
|
|
self.tile_sample_min_height = (
|
|
tile_sample_min_height or self.tile_sample_min_height
|
|
)
|
|
self.tile_sample_min_width = tile_sample_min_width or self.tile_sample_min_width
|
|
self.tile_sample_min_num_frames = (
|
|
tile_sample_min_num_frames or self.tile_sample_min_num_frames
|
|
)
|
|
self.tile_sample_stride_height = (
|
|
tile_sample_stride_height or self.tile_sample_stride_height
|
|
)
|
|
self.tile_sample_stride_width = (
|
|
tile_sample_stride_width or self.tile_sample_stride_width
|
|
)
|
|
self.tile_sample_stride_num_frames = (
|
|
tile_sample_stride_num_frames or self.tile_sample_stride_num_frames
|
|
)
|
|
if blend_num_frames is not None:
|
|
self.blend_num_frames = blend_num_frames
|
|
else:
|
|
self.blend_num_frames = (
|
|
self.tile_sample_min_num_frames - self.tile_sample_stride_num_frames
|
|
)
|
|
self.use_tiling = use_tiling or self.use_tiling
|
|
self.use_temporal_tiling = use_temporal_tiling or self.use_temporal_tiling
|
|
self.use_parallel_tiling = use_parallel_tiling or self.use_parallel_tiling
|
|
|
|
def disable_tiling(self) -> None:
|
|
r"""
|
|
Disable tiled VAE decoding. If `enable_tiling` was previously enabled, this method will go back to computing
|
|
decoding in one step.
|
|
"""
|
|
self.use_tiling = False
|
|
|
|
|
|
# adapted from https://github.com/huggingface/diffusers/blob/e7ffeae0a191f710881d1fbde00cd6ff025e81f2/src/diffusers/models/autoencoders/vae.py#L691
|
|
class DiagonalGaussianDistribution:
|
|
|
|
def __init__(self, parameters: torch.Tensor, deterministic: bool = False):
|
|
self.parameters = parameters
|
|
self.mean, self.logvar = torch.chunk(parameters, 2, dim=1)
|
|
self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
|
|
self.deterministic = deterministic
|
|
self.std = torch.exp(0.5 * self.logvar)
|
|
self.var = torch.exp(self.logvar)
|
|
if self.deterministic:
|
|
self.var = self.std = torch.zeros_like(
|
|
self.mean, device=self.parameters.device, dtype=self.parameters.dtype
|
|
)
|
|
|
|
def sample(self, generator: torch.Generator | None = None) -> torch.Tensor:
|
|
# make sure sample is on the same device as the parameters and has same dtype
|
|
sample = randn_tensor(
|
|
self.mean.shape,
|
|
generator=generator,
|
|
device=self.parameters.device,
|
|
dtype=self.parameters.dtype,
|
|
)
|
|
x = self.mean + self.std * sample
|
|
return x
|
|
|
|
def kl(
|
|
self,
|
|
other: Optional["DiagonalGaussianDistribution"] = None,
|
|
dims: tuple[int, ...] = (1, 2, 3),
|
|
) -> torch.Tensor:
|
|
if self.deterministic:
|
|
return torch.Tensor([0.0])
|
|
else:
|
|
if other is None:
|
|
return 0.5 * torch.sum(
|
|
torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
|
|
dim=dims,
|
|
)
|
|
else:
|
|
return 0.5 * torch.sum(
|
|
torch.pow(self.mean - other.mean, 2) / other.var
|
|
+ self.var / other.var
|
|
- 1.0
|
|
- self.logvar
|
|
+ other.logvar,
|
|
dim=dims,
|
|
)
|
|
|
|
def nll(
|
|
self, sample: torch.Tensor, dims: tuple[int, ...] = (1, 2, 3)
|
|
) -> torch.Tensor:
|
|
if self.deterministic:
|
|
return torch.Tensor([0.0])
|
|
logtwopi = np.log(2.0 * np.pi)
|
|
return 0.5 * torch.sum(
|
|
logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
|
|
dim=dims,
|
|
)
|
|
|
|
def mode(self) -> torch.Tensor:
|
|
return self.mean
|