[Core] Add batch parallel VAE decode (#8162)

Signed-off-by: QianCyrus <101633534+QianCyrus@users.noreply.github.com>
This commit is contained in:
QianCyrus authored and GitHub committed 2026-10-06 13:51:35 +08:00
1 parent 2bcd1e449a
commit c2cd13c009
12 files changed
+460 -37

No files matched your search

@@ -1,11 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import math
from typing import Any
import torch
import torch.distributed as dist
from diffusers.models.autoencoders import AutoencoderKL as Diffusers_AutoencoderKL
from diffusers.models.autoencoders.vae import DecoderOutput
from vllm.logger import init_logger
from vllm_omni.diffusion.distributed.autoencoders.distributed_vae_executor import (
@@ -14,6 +16,11 @@ from vllm_omni.diffusion.distributed.autoencoders.distributed_vae_executor impor
GridSpec,
TileTask,
)
from vllm_omni.diffusion.distributed.parallel_state import (
get_classifier_free_guidance_world_size,
get_data_parallel_world_size,
get_pipeline_parallel_world_size,
)
# from vllm_omni.diffusion.models.nextstep_1_1.modeling_flux_vae import AutoencoderKL as Next_Step_AutoencoderKL
@@ -34,6 +41,49 @@ class DistributedAutoencoderKL_base(DistributedVaeMixin):
model.init_distributed()
return model
def batch_split(self, z: torch.Tensor) -> tuple[list[TileTask], GridSpec]:
executor = self.distributed_executor
num_chunks = min(z.shape[0], executor.parallel_size, executor.world_size)
tasks = [
TileTask(index, (index,), chunk, workload=chunk.shape[0])
for index, chunk in enumerate(z.tensor_split(num_chunks, dim=0))
]
# The concrete diffusers base supplies dtype through the mixin MRO.
output_dtype = (
torch.get_autocast_dtype(z.device.type) if torch.is_autocast_enabled(z.device.type) else self.dtype # type: ignore[attr-defined]
)
return tasks, GridSpec(split_dims=(0,), grid_shape=(num_chunks,), output_dtype=output_dtype)
def batch_merge(self, coord_tensor_map: dict[tuple[int, ...], torch.Tensor], grid_spec: GridSpec) -> torch.Tensor:
return torch.cat([coord_tensor_map[(index,)] for index in range(grid_spec.grid_shape[0])], dim=0)
def _batch_parallel_decode(self, z: torch.Tensor, return_dict: bool = True, *args: Any, **kwargs: Any):
# Resolve the native decoder before defining the task callback. Calling
# self.decode here would recursively enter the distributed path. The
# concrete diffusers base supplies decode through the mixin MRO.
native_decode = super().decode # type: ignore[misc]
executor = self.distributed_executor
if z.shape[0] <= 1 or min(executor.parallel_size, executor.world_size) <= 1 or not dist.is_initialized():
return native_decode(z, return_dict, *args, **kwargs)
if (
get_data_parallel_world_size() > 1
or get_pipeline_parallel_world_size() > 1
or get_classifier_free_guidance_world_size() > 1
):
raise ValueError("VAE batch parallel decode requires DP, PP, and CFG parallel sizes to be 1")
def decode_chunk(task: TileTask) -> torch.Tensor:
# Keep native slicing/tiling and decoder arguments for each complete
# image chunk; only the batch dimension is distributed.
return native_decode(task.tensor, False, *args, **kwargs)[0]
result = executor.execute(
z,
DistributedOperator(split=self.batch_split, exec=decode_chunk, merge=self.batch_merge),
broadcast_result=True,
)
return DecoderOutput(sample=result) if return_dict else (result,)
def tile_split(self, z: torch.Tensor) -> tuple[list[TileTask], GridSpec]:
# mostly copy from AutoencoderKL
overlap_size = int(self.tile_latent_min_size * (1 - self.tile_overlap_factor))
@@ -185,6 +235,8 @@ class DistributedAutoencoderKL_base(DistributedVaeMixin):
# Normally, we should override tiled_decode. However, since we also need to
# support the patch split strategy, we override decode instead.
def decode(self, z: torch.Tensor, return_dict: bool = True, *args: Any, **kwargs: Any):
if self.distributed_executor.parallel_mode == "batch":
return self._batch_parallel_decode(z, return_dict, *args, **kwargs)
if not self.is_distributed_enabled():
return super().decode(z, return_dict=return_dict, *args, **kwargs)