[diffusion] feat: generalize layerwise offload residency mixin to all components (#24593)
This commit is contained in:
@@ -230,6 +230,7 @@ class PipelineConfig:
|
||||
# Image encoder configuration
|
||||
image_encoder_config: EncoderConfig = field(default_factory=EncoderConfig)
|
||||
image_encoder_precision: str = "fp32"
|
||||
image_encoder_extra_args: dict = field(default_factory=lambda: {})
|
||||
|
||||
# Text encoder configuration
|
||||
DEFAULT_TEXT_ENCODER_PRECISIONS = ("fp32",)
|
||||
@@ -240,9 +241,6 @@ class PipelineConfig:
|
||||
text_encoder_precisions: tuple[str, ...] = field(default_factory=lambda: ("fp32",))
|
||||
text_encoder_extra_args: list[dict] = field(default_factory=lambda: [{}])
|
||||
|
||||
# image encoding
|
||||
image_encoder_extra_args: dict = field(default_factory=lambda: {})
|
||||
|
||||
def get_model_deployment_config(self) -> ModelDeploymentConfig:
|
||||
return ModelDeploymentConfig()
|
||||
|
||||
@@ -266,6 +264,10 @@ class PipelineConfig:
|
||||
# DMD parameters
|
||||
dmd_denoising_steps: list[int] | None = field(default=None)
|
||||
|
||||
def get_model_deployment_config(self) -> ModelDeploymentConfig:
|
||||
# return the model-specific config for optimal deployment setting
|
||||
return ModelDeploymentConfig()
|
||||
|
||||
# Wan2.2 TI2V parameters
|
||||
boundary_ratio: float | None = None
|
||||
|
||||
|
||||
@@ -88,8 +88,6 @@ class WanT2V480PConfig(PipelineConfig):
|
||||
vae_precision: str = "fp32"
|
||||
text_encoder_precisions: tuple[str, ...] = field(default_factory=lambda: ("fp32",))
|
||||
|
||||
# WanConfig-specific added parameters
|
||||
|
||||
def __post_init__(self):
|
||||
self.vae_config.load_encoder = False
|
||||
self.vae_config.load_decoder = True
|
||||
|
||||
@@ -25,6 +25,15 @@ from sglang.multimodal_gen.runtime.loader.utils import (
|
||||
component_name_to_loader_cls,
|
||||
get_memory_usage_of_component,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
configure_layerwise_offload_modules,
|
||||
is_layerwise_offloaded_module,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload_components import (
|
||||
LAYERWISE_OFFLOAD_ALL_COMPONENTS,
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS,
|
||||
layerwise_component_matches_selection,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
|
||||
@@ -96,6 +105,66 @@ class ComponentLoader(ABC):
|
||||
else:
|
||||
return get_local_torch_device()
|
||||
|
||||
def customized_load_kwargs_for_component(
|
||||
self, _server_args: ServerArgs, _component_name: str
|
||||
) -> dict[str, Any]:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _is_component_set_as_layerwise_load(
|
||||
server_args: ServerArgs, component_name: str
|
||||
) -> bool:
|
||||
"""if a component should be loaded in a layerwise-fashion"""
|
||||
selected_component_names = server_args.layerwise_offload_components
|
||||
if selected_component_names is None:
|
||||
return False
|
||||
selected_component_names = set(selected_component_names)
|
||||
if LAYERWISE_OFFLOAD_ALL_COMPONENTS in selected_component_names:
|
||||
return True
|
||||
explicit_component_names = selected_component_names - {
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS
|
||||
}
|
||||
return any(
|
||||
layerwise_component_matches_selection(component_name, selected_component)
|
||||
for selected_component in explicit_component_names
|
||||
)
|
||||
|
||||
def _maybe_configure_layerwise_after_startup_cpu_staging(
|
||||
self,
|
||||
component: AutoModel,
|
||||
server_args: ServerArgs,
|
||||
component_name: str,
|
||||
load_kwargs: dict[str, Any],
|
||||
) -> AutoModel:
|
||||
if not load_kwargs.get("cpu_offload_flag"):
|
||||
return component
|
||||
if not isinstance(component, nn.Module):
|
||||
return component
|
||||
|
||||
# try to configure layerwise-offload with the component
|
||||
configured_components = configure_layerwise_offload_modules(
|
||||
{component_name: component},
|
||||
server_args,
|
||||
component_names=server_args.layerwise_offload_components,
|
||||
warn_missing=False,
|
||||
)
|
||||
if is_layerwise_offloaded_module(component):
|
||||
logger.info(
|
||||
"Configured layerwise offload for %s immediately after startup CPU staging",
|
||||
component_name,
|
||||
)
|
||||
return component
|
||||
|
||||
logger.warning(
|
||||
"Layerwise startup CPU staging was requested for %s, but the loaded "
|
||||
"module did not enable layerwise offload. Moving it to GPU.",
|
||||
component_name,
|
||||
)
|
||||
# ensures the module is on GPU
|
||||
if component_name in configured_components:
|
||||
return component
|
||||
return component.to(get_local_torch_device())
|
||||
|
||||
def load(
|
||||
self,
|
||||
component_model_path: str,
|
||||
@@ -135,8 +204,15 @@ class ComponentLoader(ABC):
|
||||
with component_attn_backend_context_manager(
|
||||
attn_backend, component_name=component_attn_name
|
||||
):
|
||||
load_kwargs = self.customized_load_kwargs_for_component(
|
||||
server_args, component_name
|
||||
)
|
||||
component = self.load_customized(
|
||||
component_model_path, server_args, component_name
|
||||
component_model_path, server_args, component_name, **load_kwargs
|
||||
)
|
||||
# configure layerwise to make enough VRAM headroom
|
||||
component = self._maybe_configure_layerwise_after_startup_cpu_staging(
|
||||
component, server_args, component_name, load_kwargs
|
||||
)
|
||||
source = "sgl-diffusion"
|
||||
except Exception as e:
|
||||
|
||||
+10
-2
@@ -30,7 +30,11 @@ class ImageEncoderLoader(TextEncoderLoader):
|
||||
return use_cpu_offload
|
||||
|
||||
def load_customized(
|
||||
self, component_model_path: str, server_args: ServerArgs, *args
|
||||
self,
|
||||
component_model_path: str,
|
||||
server_args: ServerArgs,
|
||||
component_name: str = "image_encoder",
|
||||
cpu_offload_flag: bool | None = None,
|
||||
):
|
||||
"""Load the text encoders based on the model path, and inference args."""
|
||||
# model_config: PretrainedConfig = get_hf_config(
|
||||
@@ -53,5 +57,9 @@ class ImageEncoderLoader(TextEncoderLoader):
|
||||
encoder_config,
|
||||
server_args,
|
||||
server_args.pipeline_config.image_encoder_precision,
|
||||
cpu_offload_flag=server_args.image_encoder_cpu_offload,
|
||||
cpu_offload_flag=(
|
||||
cpu_offload_flag
|
||||
if cpu_offload_flag is not None
|
||||
else server_args.image_encoder_cpu_offload
|
||||
),
|
||||
)
|
||||
|
||||
@@ -81,6 +81,19 @@ class TextEncoderLoader(ComponentLoader):
|
||||
use_cpu_offload = should_offload and len(fsdp_shard_conditions) > 0
|
||||
return use_cpu_offload
|
||||
|
||||
def customized_load_kwargs_for_component(
|
||||
self, server_args: ServerArgs, component_name: str
|
||||
) -> dict[str, bool]:
|
||||
if ComponentLoader._is_component_set_as_layerwise_load(
|
||||
server_args, component_name
|
||||
):
|
||||
logger.info(
|
||||
"Loading %s on CPU first because it is selected for layerwise offload",
|
||||
component_name,
|
||||
)
|
||||
return {"cpu_offload_flag": True}
|
||||
return {}
|
||||
|
||||
def load_native(
|
||||
self,
|
||||
component_model_path: str,
|
||||
|
||||
@@ -53,7 +53,9 @@ from sglang.multimodal_gen.runtime.loader.utils import (
|
||||
from sglang.multimodal_gen.runtime.loader.weight_utils import (
|
||||
safetensors_weights_iterator,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
is_layerwise_offloaded_module,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines.diffusers_pipeline import DiffusersPipeline
|
||||
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import maybe_download_model
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -114,7 +116,7 @@ def _load_weights_into_module(module: torch.nn.Module, weights_iter) -> None:
|
||||
update_cpu_weights(); non-offloaded parameters use in-place copy.
|
||||
"""
|
||||
offload_managers: list = []
|
||||
if isinstance(module, OffloadableDiTMixin) and module.layerwise_offload_managers:
|
||||
if is_layerwise_offloaded_module(module):
|
||||
offload_managers = [m for m in module.layerwise_offload_managers if m.enabled]
|
||||
|
||||
if offload_managers:
|
||||
|
||||
@@ -160,7 +160,7 @@ class BatchAdmissionController:
|
||||
self._mode = getattr(server_args, "batching_mode", "dynamic")
|
||||
self._user_max_batch_size = max(1, int(server_args.batching_max_size))
|
||||
self._model_path = server_args.model_path
|
||||
self._offload = bool(server_args.dit_layerwise_offload)
|
||||
self._offload = bool(server_args.layerwise_offload_components)
|
||||
self._device_memory_gb = self._get_device_memory_gb(gpu_id)
|
||||
self._rules = load_batching_config(server_args.batching_config)
|
||||
self._pipeline_config = server_args.pipeline_config
|
||||
|
||||
@@ -41,8 +41,8 @@ from sglang.multimodal_gen.runtime.loader.weights_updater import (
|
||||
WeightsUpdater,
|
||||
get_updatable_modules,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import (
|
||||
OffloadableDiTMixin,
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
configure_layerwise_offload_modules,
|
||||
iter_materialized_weights,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core import (
|
||||
@@ -165,23 +165,12 @@ class GPUWorker:
|
||||
|
||||
# apply layerwise offload after lora is applied while building LoRAPipeline
|
||||
# otherwise empty offloaded weights could fail lora converting
|
||||
if self.server_args.dit_layerwise_offload:
|
||||
# enable layerwise offload if possible
|
||||
for module_name in [
|
||||
"transformer",
|
||||
"transformer_2",
|
||||
"video_dit",
|
||||
"video_dit_2",
|
||||
"audio_dit",
|
||||
]:
|
||||
dit = self.pipeline.get_module(module_name)
|
||||
if dit:
|
||||
if isinstance(dit, OffloadableDiTMixin):
|
||||
dit.configure_layerwise_offload(self.server_args)
|
||||
else:
|
||||
logger.info(
|
||||
f"Module {type(dit).__name__} does not support layerwise offload. Skipping."
|
||||
)
|
||||
if self.server_args.layerwise_offload_components:
|
||||
configure_layerwise_offload_modules(
|
||||
self.pipeline.modules,
|
||||
self.server_args,
|
||||
component_names=self.server_args.layerwise_offload_components,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Worker {self.rank}: Initialized device, model, and distributed environment."
|
||||
@@ -234,7 +223,7 @@ class GPUWorker:
|
||||
elif component in ("text_encoder", "text_encoder_2"):
|
||||
arg = "--text-encoder-cpu-offload"
|
||||
elif component == "transformer":
|
||||
if self.server_args.dit_layerwise_offload:
|
||||
if self.server_args.is_dit_layerwise_offload_selected:
|
||||
arg = "--dit-layerwise-offload"
|
||||
elif self.server_args.dit_cpu_offload:
|
||||
arg = "--dit-cpu-offload"
|
||||
@@ -740,7 +729,7 @@ class GPUWorker:
|
||||
# If the flag is True, it is currently offloaded, so it is a candidate to stay resident.
|
||||
offload_flags = {
|
||||
"transformer": self.server_args.dit_cpu_offload
|
||||
or self.server_args.dit_layerwise_offload,
|
||||
or self.server_args.is_dit_layerwise_offload_selected,
|
||||
"vae": self.server_args.vae_cpu_offload,
|
||||
"text_encoder": self.server_args.text_encoder_cpu_offload,
|
||||
"text_encoder_2": self.server_args.text_encoder_cpu_offload,
|
||||
|
||||
+30
-57
@@ -7,13 +7,21 @@ from typing import Mapping, MutableMapping, Protocol, Sequence, TypeVar
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.component_resident_strategies import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_resident_strategies import (
|
||||
ComponentResidencyStrategy,
|
||||
LayerwiseOffloadStrategy,
|
||||
ResidentStrategy,
|
||||
VanillaD2HStrategy,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
is_layerwise_offloaded_module,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload_components import (
|
||||
is_dit_component_name,
|
||||
is_image_encoder_component_name,
|
||||
is_text_encoder_component_name,
|
||||
is_vae_component_name,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -85,70 +93,35 @@ class ComponentResidencyPipeline(Protocol):
|
||||
component_residency_strategies: MutableMapping[str, "ComponentResidencyStrategy"]
|
||||
|
||||
|
||||
def build_dit_residency_strategy(
|
||||
module: nn.Module,
|
||||
server_args: ServerArgs,
|
||||
) -> ComponentResidencyStrategy:
|
||||
if (
|
||||
isinstance(module, OffloadableDiTMixin)
|
||||
and module.layerwise_offload_managers
|
||||
and any(manager.enabled for manager in module.layerwise_offload_managers)
|
||||
):
|
||||
# only if dit_layerwise_offload is enabled
|
||||
return LayerwiseOffloadStrategy()
|
||||
if server_args.dit_cpu_offload and not server_args.use_fsdp_inference:
|
||||
# handles offload by vanalla D2H
|
||||
return VanillaD2HStrategy()
|
||||
return ResidentStrategy()
|
||||
|
||||
|
||||
def is_fsdp_managed_module(module: nn.Module) -> bool:
|
||||
return module.__class__.__name__.startswith("FSDP")
|
||||
|
||||
|
||||
def should_cpu_offload_component(
|
||||
component_name: str, module: nn.Module, server_args: ServerArgs
|
||||
) -> bool:
|
||||
if server_args.use_fsdp_inference or is_fsdp_managed_module(module):
|
||||
return False
|
||||
if is_dit_component_name(component_name):
|
||||
return bool(server_args.dit_cpu_offload)
|
||||
if is_text_encoder_component_name(component_name):
|
||||
return bool(server_args.text_encoder_cpu_offload)
|
||||
if is_image_encoder_component_name(component_name):
|
||||
return bool(server_args.image_encoder_cpu_offload)
|
||||
if is_vae_component_name(component_name):
|
||||
return bool(server_args.vae_cpu_offload)
|
||||
return False
|
||||
|
||||
|
||||
def build_component_residency_strategy(
|
||||
component_name: str,
|
||||
module: nn.Module,
|
||||
server_args: ServerArgs,
|
||||
) -> ComponentResidencyStrategy:
|
||||
if component_name in {
|
||||
"transformer",
|
||||
"transformer_2",
|
||||
"video_dit",
|
||||
"video_dit_2",
|
||||
"audio_dit",
|
||||
"dual_tower_bridge",
|
||||
}:
|
||||
return build_dit_residency_strategy(module, server_args)
|
||||
|
||||
if component_name.startswith("text_encoder") or component_name.endswith(
|
||||
"text_encoder"
|
||||
):
|
||||
if (
|
||||
server_args.text_encoder_cpu_offload
|
||||
and not server_args.use_fsdp_inference
|
||||
and not is_fsdp_managed_module(module)
|
||||
):
|
||||
return VanillaD2HStrategy()
|
||||
return ResidentStrategy()
|
||||
|
||||
if component_name == "image_encoder":
|
||||
if server_args.image_encoder_cpu_offload and not server_args.use_fsdp_inference:
|
||||
return VanillaD2HStrategy()
|
||||
return ResidentStrategy()
|
||||
|
||||
if component_name in {
|
||||
"vae",
|
||||
"video_vae",
|
||||
"audio_vae",
|
||||
"vocoder",
|
||||
"spatial_upsampler",
|
||||
"condition_image_encoder",
|
||||
}:
|
||||
if server_args.vae_cpu_offload and not server_args.use_fsdp_inference:
|
||||
return VanillaD2HStrategy()
|
||||
return ResidentStrategy()
|
||||
|
||||
if is_layerwise_offloaded_module(module):
|
||||
return LayerwiseOffloadStrategy()
|
||||
if should_cpu_offload_component(component_name, module, server_args):
|
||||
return VanillaD2HStrategy()
|
||||
return ResidentStrategy()
|
||||
|
||||
|
||||
+6
-4
@@ -10,12 +10,14 @@ import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
ResidencyState,
|
||||
)
|
||||
@@ -484,11 +486,11 @@ class LayerwiseOffloadStrategy(ComponentResidencyStrategy):
|
||||
name = "layerwise"
|
||||
|
||||
def enter(self, module: nn.Module) -> None:
|
||||
if isinstance(module, OffloadableDiTMixin):
|
||||
if isinstance(module, LayerwiseOffloadableModuleMixin):
|
||||
module.prepare_for_next_req()
|
||||
|
||||
def exit(self, module: nn.Module, next_module: nn.Module | None = None) -> None:
|
||||
if not isinstance(module, OffloadableDiTMixin):
|
||||
if not isinstance(module, LayerwiseOffloadableModuleMixin):
|
||||
return
|
||||
for manager in module.layerwise_offload_managers:
|
||||
manager.release_all()
|
||||
+217
-48
@@ -1,9 +1,14 @@
|
||||
import re
|
||||
from itertools import chain
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any, Dict, List, Set, Tuple
|
||||
|
||||
import torch
|
||||
from torch.distributed.tensor import DTensor
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload_components import (
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS,
|
||||
layerwise_component_matches_selection,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -70,6 +75,7 @@ class LayerwiseOffloadManager:
|
||||
self._named_parameters: Dict[str, torch.nn.Parameter] = {}
|
||||
self._named_buffers: Dict[str, torch.Tensor] = {}
|
||||
self._offload_placeholders: Dict[torch.dtype, torch.Tensor] = {}
|
||||
self._has_dtensor_weights = False
|
||||
# Store forward hooks for removal
|
||||
self._forward_hooks: List[Any] = []
|
||||
|
||||
@@ -91,6 +97,26 @@ class LayerwiseOffloadManager:
|
||||
self._offload_placeholders[dtype] = placeholder
|
||||
return placeholder
|
||||
|
||||
@staticmethod
|
||||
def _to_local_tensor(tensor: torch.Tensor) -> torch.Tensor:
|
||||
if isinstance(tensor, DTensor):
|
||||
return tensor.to_local()
|
||||
return tensor
|
||||
|
||||
def _wrap_for_target(
|
||||
self, target: torch.Tensor, local_tensor: torch.Tensor
|
||||
) -> torch.Tensor:
|
||||
if isinstance(target, DTensor):
|
||||
return DTensor.from_local(
|
||||
local_tensor, target.device_mesh, target.placements
|
||||
)
|
||||
return local_tensor
|
||||
|
||||
def _get_shared_empty_tensor_for_target(
|
||||
self, target: torch.Tensor, dtype: torch.dtype
|
||||
) -> torch.Tensor:
|
||||
return self._wrap_for_target(target, self._get_shared_empty_tensor(dtype))
|
||||
|
||||
@staticmethod
|
||||
def _get_alignment_numel(dtype: torch.dtype, alignment_bytes: int = 32) -> int:
|
||||
element_size = torch.empty((), dtype=dtype).element_size()
|
||||
@@ -114,16 +140,20 @@ class LayerwiseOffloadManager:
|
||||
self._named_parameters = dict(self.model.named_parameters())
|
||||
self._named_buffers = dict(self.model.named_buffers())
|
||||
|
||||
# 1. collect and group tensors by layer and dtype
|
||||
# 1. collect and group layer parameters by dtype. Keep buffers resident:
|
||||
# shared buffers such as RoPE caches may be referenced by many layers.
|
||||
layer_groups: Dict[int, Dict[torch.dtype, List[Tuple[str, torch.Tensor]]]] = {}
|
||||
all_tensors = chain(self._named_parameters.items(), self._named_buffers.items())
|
||||
for name, tensor in all_tensors:
|
||||
for name, tensor in self._named_parameters.items():
|
||||
layer_idx = self._match_layer_idx(name)
|
||||
if layer_idx is None or layer_idx >= self.num_layers:
|
||||
continue
|
||||
layer_groups.setdefault(layer_idx, {}).setdefault(tensor.dtype, []).append(
|
||||
(name, tensor)
|
||||
self._has_dtensor_weights = self._has_dtensor_weights or isinstance(
|
||||
tensor, DTensor
|
||||
)
|
||||
local_tensor = self._to_local_tensor(tensor)
|
||||
layer_groups.setdefault(layer_idx, {}).setdefault(
|
||||
local_tensor.dtype, []
|
||||
).append((name, tensor))
|
||||
|
||||
# 2. concat and offload (in pinned memory)
|
||||
for layer_idx, dtype_to_params in layer_groups.items():
|
||||
@@ -132,43 +162,46 @@ class LayerwiseOffloadManager:
|
||||
self._weight_metadata[layer_idx] = {}
|
||||
|
||||
for dtype, weights in dtype_to_params.items():
|
||||
contiguous_weights: List[Tuple[str, torch.Tensor]] = []
|
||||
contiguous_weights: List[Tuple[str, torch.Tensor, torch.Tensor]] = []
|
||||
for name, weight in weights:
|
||||
if weight.is_contiguous():
|
||||
contiguous_weights.append((name, weight))
|
||||
local_weight = self._to_local_tensor(weight)
|
||||
if local_weight.is_contiguous():
|
||||
contiguous_weights.append((name, weight, local_weight))
|
||||
continue
|
||||
|
||||
# Preserve non-contiguous layouts such as the transposed FP8
|
||||
# weight views expected by CUTLASS kernels.
|
||||
cpu_tensor = torch.empty_strided(
|
||||
size=weight.shape,
|
||||
stride=weight.stride(),
|
||||
size=local_weight.shape,
|
||||
stride=local_weight.stride(),
|
||||
dtype=dtype,
|
||||
pin_memory=self.pin_cpu_memory,
|
||||
)
|
||||
cpu_tensor.copy_(weight)
|
||||
cpu_tensor.copy_(local_weight)
|
||||
self._strided_cpu_weights[layer_idx][name] = cpu_tensor
|
||||
self._weight_metadata[layer_idx][name] = {
|
||||
"dtype": dtype,
|
||||
"shape": weight.shape,
|
||||
"stride": weight.stride(),
|
||||
"shape": local_weight.shape,
|
||||
"stride": local_weight.stride(),
|
||||
"preserve_strides": True,
|
||||
}
|
||||
weight.data = self._get_shared_empty_tensor(dtype)
|
||||
weight.data = self._get_shared_empty_tensor_for_target(
|
||||
weight, dtype
|
||||
)
|
||||
|
||||
if not contiguous_weights:
|
||||
continue
|
||||
|
||||
current_offset = 0
|
||||
aligned_offsets: Dict[str, int] = {}
|
||||
for name, weight in contiguous_weights:
|
||||
for name, weight, local_weight in contiguous_weights:
|
||||
# Some fused diffusion kernels require tensor base pointers to
|
||||
# satisfy a 32-byte alignment contract. Reusing one flat buffer
|
||||
# is still fine, but each logical tensor slice must start on an
|
||||
# aligned offset inside that buffer.
|
||||
current_offset = self._align_numel_offset(current_offset, dtype)
|
||||
aligned_offsets[name] = current_offset
|
||||
current_offset += weight.numel()
|
||||
current_offset += local_weight.numel()
|
||||
|
||||
total_numel = current_offset
|
||||
|
||||
@@ -178,22 +211,24 @@ class LayerwiseOffloadManager:
|
||||
)
|
||||
|
||||
# offload weights to the buffer
|
||||
for name, weight in contiguous_weights:
|
||||
for name, weight, local_weight in contiguous_weights:
|
||||
current_offset = aligned_offsets[name]
|
||||
numel = weight.numel()
|
||||
numel = local_weight.numel()
|
||||
cpu_buffer[current_offset : current_offset + numel].copy_(
|
||||
weight.flatten()
|
||||
local_weight.flatten()
|
||||
)
|
||||
self._weight_metadata[layer_idx][name] = {
|
||||
"dtype": dtype,
|
||||
"offset": current_offset,
|
||||
"numel": numel,
|
||||
"shape": weight.shape,
|
||||
"stride": weight.stride(),
|
||||
"shape": local_weight.shape,
|
||||
"stride": local_weight.stride(),
|
||||
"preserve_strides": False,
|
||||
}
|
||||
|
||||
weight.data = self._get_shared_empty_tensor(dtype)
|
||||
weight.data = self._get_shared_empty_tensor_for_target(
|
||||
weight, dtype
|
||||
)
|
||||
|
||||
current_offset += numel
|
||||
|
||||
@@ -202,7 +237,8 @@ class LayerwiseOffloadManager:
|
||||
# Keep non-layer parameters resident on GPU. Layer tensors have already
|
||||
# been replaced by tiny device placeholders, so this does not reload the
|
||||
# offloaded layer weights.
|
||||
self.model.to(self.device)
|
||||
if not self._has_dtensor_weights:
|
||||
self.model.to(self.device)
|
||||
|
||||
# prefetch the first layer for warm-up
|
||||
self.prepare_for_next_req(non_blocking=False)
|
||||
@@ -271,16 +307,17 @@ class LayerwiseOffloadManager:
|
||||
device=self.device,
|
||||
)
|
||||
gpu_tensor.copy_(cpu_tensor, non_blocking=non_blocking)
|
||||
target.data = gpu_tensor
|
||||
target.data = self._wrap_for_target(target, gpu_tensor)
|
||||
continue
|
||||
|
||||
dtype = meta["dtype"]
|
||||
gpu_buffer = gpu_buffers[dtype]
|
||||
|
||||
# map the parameter's data to the correct slice of the GPU buffer
|
||||
target.data = gpu_buffer[
|
||||
local_tensor = gpu_buffer[
|
||||
meta["offset"] : meta["offset"] + meta["numel"]
|
||||
].view(meta["shape"])
|
||||
target.data = self._wrap_for_target(target, local_tensor)
|
||||
|
||||
# record the prefetch event of this layer after all copies are enqueued
|
||||
event = torch.get_device_module().Event()
|
||||
@@ -307,7 +344,9 @@ class LayerwiseOffloadManager:
|
||||
for name, meta in self._weight_metadata.get(layer_idx, {}).items():
|
||||
target = self.get_target_with_name(name)
|
||||
# Wraparound prefetch will reload the layer when it is needed again
|
||||
target.data = self._get_shared_empty_tensor(meta["dtype"])
|
||||
target.data = self._get_shared_empty_tensor_for_target(
|
||||
target, meta["dtype"]
|
||||
)
|
||||
|
||||
self._gpu_layers.discard(layer_idx)
|
||||
|
||||
@@ -347,11 +386,12 @@ class LayerwiseOffloadManager:
|
||||
# Collect current GPU weights and write back to CPU buffer
|
||||
for name, meta in self._weight_metadata.get(layer_idx, {}).items():
|
||||
target = self.get_target_with_name(name)
|
||||
target_local = self._to_local_tensor(target)
|
||||
if meta.get("preserve_strides", False):
|
||||
self._strided_cpu_weights[layer_idx][name].copy_(target.data.cpu())
|
||||
self._strided_cpu_weights[layer_idx][name].copy_(target_local.cpu())
|
||||
continue
|
||||
|
||||
gpu_weight = target.data.flatten().cpu()
|
||||
gpu_weight = target_local.flatten().cpu()
|
||||
|
||||
dtype = meta["dtype"]
|
||||
cpu_buffer = self._consolidated_cpu_weights[layer_idx][dtype]
|
||||
@@ -408,30 +448,32 @@ class LayerwiseOffloadManager:
|
||||
continue
|
||||
|
||||
meta = meta_layer[name]
|
||||
if tuple(meta["shape"]) != tuple(loaded_weight.shape):
|
||||
local_loaded_weight = self._to_local_tensor(loaded_weight)
|
||||
if tuple(meta["shape"]) != tuple(local_loaded_weight.shape):
|
||||
raise ValueError(
|
||||
f"Shape mismatch for {name}: "
|
||||
f"expected={tuple(meta['shape'])}, "
|
||||
f"loaded={tuple(loaded_weight.shape)}"
|
||||
f"loaded={tuple(local_loaded_weight.shape)}"
|
||||
)
|
||||
|
||||
dtype = meta["dtype"]
|
||||
if meta.get("preserve_strides", False):
|
||||
self._strided_cpu_weights[layer_idx][name].copy_(
|
||||
loaded_weight.to(dtype=dtype)
|
||||
local_loaded_weight.to(dtype=dtype)
|
||||
)
|
||||
else:
|
||||
offset = meta["offset"]
|
||||
numel = meta["numel"]
|
||||
cpu_buffer = self._consolidated_cpu_weights[layer_idx][dtype]
|
||||
cpu_buffer[offset : offset + numel].copy_(
|
||||
loaded_weight.to(dtype=dtype).flatten()
|
||||
local_loaded_weight.to(dtype=dtype).flatten()
|
||||
)
|
||||
|
||||
# If this layer is currently on GPU, update the live parameter.
|
||||
if layer_idx in self._gpu_layers:
|
||||
target = self.get_target_with_name(name)
|
||||
target.data.copy_(loaded_weight.to(dtype=target.dtype))
|
||||
target_local = self._to_local_tensor(target)
|
||||
target_local.copy_(local_loaded_weight.to(dtype=target_local.dtype))
|
||||
|
||||
updated_names.add(name)
|
||||
|
||||
@@ -467,7 +509,7 @@ class LayerwiseOffloadManager:
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
layers = getattr(self.model, self.layers_attr_str)
|
||||
layers = dict(self.model.named_modules())[self.layers_attr_str]
|
||||
|
||||
def make_pre_hook(i):
|
||||
def hook(module, input):
|
||||
@@ -509,21 +551,24 @@ class LayerwiseOffloadManager:
|
||||
self._forward_hooks.clear()
|
||||
|
||||
|
||||
class OffloadableDiTMixin:
|
||||
"""
|
||||
A mixin that registers forward hooks for a DiT to enable layerwise offload
|
||||
"""
|
||||
class LayerwiseOffloadableModuleMixin:
|
||||
"""A mixin that registers forward hooks to enable layerwise offload."""
|
||||
|
||||
# the list of names of a DiT's layers/blocks
|
||||
layer_names: List[str]
|
||||
# Legacy --dit-layerwise-offload configures these modules when no component is named.
|
||||
layerwise_offload_default_enabled: bool = True
|
||||
# The list of names of this module's layer/block ModuleList or Sequential attributes.
|
||||
layer_names: List[str] = []
|
||||
layerwise_offload_managers: list[LayerwiseOffloadManager] = []
|
||||
|
||||
def configure_layerwise_offload(self, server_args: ServerArgs):
|
||||
self.layerwise_offload_managers = []
|
||||
named_modules = dict(self.named_modules())
|
||||
configured_layer_names = []
|
||||
for layer_name in self.layer_names:
|
||||
# a manager per layer-list
|
||||
module_list = getattr(self, layer_name, None)
|
||||
if module_list is None or not isinstance(module_list, torch.nn.ModuleList):
|
||||
module_list = named_modules.get(layer_name)
|
||||
if not isinstance(module_list, (torch.nn.ModuleList, torch.nn.Sequential)):
|
||||
continue
|
||||
if len(module_list) == 0:
|
||||
continue
|
||||
|
||||
num_layers = len(module_list)
|
||||
@@ -543,10 +588,20 @@ class OffloadableDiTMixin:
|
||||
prefetch_size=prefetch_size,
|
||||
)
|
||||
self.layerwise_offload_managers.append(manager)
|
||||
configured_layer_names.append(layer_name)
|
||||
|
||||
logger.info(
|
||||
f"Enabled layerwise offload for {self.__class__.__name__} on modules: {self.layer_names}"
|
||||
)
|
||||
if configured_layer_names:
|
||||
logger.info(
|
||||
"Enabled layerwise offload for %s on modules: %s",
|
||||
self.__class__.__name__,
|
||||
configured_layer_names,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
"No layerwise-offloadable ModuleList found for %s. Candidates: %s",
|
||||
self.__class__.__name__,
|
||||
self.layer_names,
|
||||
)
|
||||
|
||||
def prepare_for_next_req(self):
|
||||
if self.layerwise_offload_managers is None:
|
||||
@@ -583,7 +638,7 @@ def iter_materialized_weights(module: torch.nn.Module):
|
||||
the non-offloaded parameters.
|
||||
"""
|
||||
offload_managers: list = []
|
||||
if isinstance(module, OffloadableDiTMixin) and module.layerwise_offload_managers:
|
||||
if is_layerwise_offloaded_module(module):
|
||||
offload_managers = [m for m in module.layerwise_offload_managers if m.enabled]
|
||||
|
||||
if not offload_managers:
|
||||
@@ -601,3 +656,117 @@ def iter_materialized_weights(module: torch.nn.Module):
|
||||
for name, param in module.named_parameters():
|
||||
if name not in offloaded_names:
|
||||
yield name, param
|
||||
|
||||
|
||||
def is_layerwise_offloaded_module(module: torch.nn.Module) -> bool:
|
||||
return isinstance(module, LayerwiseOffloadableModuleMixin) and any(
|
||||
manager.enabled for manager in module.layerwise_offload_managers
|
||||
)
|
||||
|
||||
|
||||
def configure_layerwise_offload_modules(
|
||||
modules: Mapping[str, object],
|
||||
server_args: ServerArgs,
|
||||
component_names: Sequence[str] | None = None,
|
||||
warn_missing: bool = True,
|
||||
) -> list[str]:
|
||||
"""Configure layerwise offload for the given modules, from the given component_names
|
||||
|
||||
Args:
|
||||
modules: the dict of {component_name: component}, containing the components to be chosen from
|
||||
component_names: list of component names. component with names not in this list shouldn't be configured
|
||||
|
||||
Returns a list of component names of modules configured to be layerwise-offload
|
||||
"""
|
||||
|
||||
# components which has already been configured to be layerwise-offload
|
||||
configured_component_names: list[str] = []
|
||||
configured_module_ids: set[int] = set()
|
||||
selected_component_names = (
|
||||
set(component_names) if component_names is not None else None
|
||||
)
|
||||
select_all = (
|
||||
selected_component_names is not None and "all" in selected_component_names
|
||||
)
|
||||
select_default = (
|
||||
selected_component_names is not None
|
||||
and LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS in selected_component_names
|
||||
)
|
||||
|
||||
if warn_missing and selected_component_names is not None and not select_all:
|
||||
explicit_component_names = selected_component_names - {
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS
|
||||
}
|
||||
missing_component_names = [
|
||||
selected_component_name
|
||||
for selected_component_name in explicit_component_names
|
||||
if not any(
|
||||
layerwise_component_matches_selection(
|
||||
component_name, selected_component_name
|
||||
)
|
||||
for component_name in modules
|
||||
)
|
||||
]
|
||||
if missing_component_names:
|
||||
logger.warning(
|
||||
"Layerwise offload components are not currently loaded: %s. "
|
||||
"Available pipeline components: %s",
|
||||
sorted(missing_component_names),
|
||||
sorted(modules),
|
||||
)
|
||||
|
||||
unsupported_component_names = [
|
||||
component_name
|
||||
for component_name in modules
|
||||
if any(
|
||||
layerwise_component_matches_selection(
|
||||
component_name, selected_component_name
|
||||
)
|
||||
for selected_component_name in explicit_component_names
|
||||
)
|
||||
if not isinstance(modules[component_name], LayerwiseOffloadableModuleMixin)
|
||||
]
|
||||
if unsupported_component_names:
|
||||
logger.warning(
|
||||
"Layerwise offload components do not support layerwise offload: %s",
|
||||
sorted(unsupported_component_names),
|
||||
)
|
||||
|
||||
for component_name, module in modules.items():
|
||||
if not isinstance(module, LayerwiseOffloadableModuleMixin):
|
||||
continue
|
||||
if selected_component_names is None:
|
||||
if not module.layerwise_offload_default_enabled:
|
||||
continue
|
||||
elif (
|
||||
not select_all
|
||||
and not any(
|
||||
layerwise_component_matches_selection(
|
||||
component_name, selected_component_name
|
||||
)
|
||||
for selected_component_name in selected_component_names
|
||||
)
|
||||
and not (select_default and module.layerwise_offload_default_enabled)
|
||||
):
|
||||
# if the current component is not selected to be layerwise-offload, skip
|
||||
continue
|
||||
module_id = id(module)
|
||||
if module_id in configured_module_ids:
|
||||
# avoid multiple configures on a same module
|
||||
continue
|
||||
|
||||
configured_module_ids.add(module_id)
|
||||
|
||||
if not is_layerwise_offloaded_module(module):
|
||||
module.configure_layerwise_offload(server_args)
|
||||
if is_layerwise_offloaded_module(module):
|
||||
configured_component_names.append(component_name)
|
||||
|
||||
if configured_component_names:
|
||||
logger.info(
|
||||
"Enabled layerwise offload for pipeline components: %s",
|
||||
configured_component_names,
|
||||
)
|
||||
else:
|
||||
logger.info("No pipeline component supports layerwise offload.")
|
||||
return configured_component_names
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
from collections.abc import Sequence
|
||||
|
||||
LAYERWISE_OFFLOAD_ALL_COMPONENTS = "all"
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS = "default"
|
||||
DIT_COMPONENT_NAMES = frozenset(
|
||||
{
|
||||
"transformer",
|
||||
"transformer_2",
|
||||
"video_dit",
|
||||
"video_dit_2",
|
||||
"audio_dit",
|
||||
"dual_tower_bridge",
|
||||
}
|
||||
)
|
||||
VAE_COMPONENT_NAMES = frozenset(
|
||||
{
|
||||
"vae",
|
||||
"video_vae",
|
||||
"audio_vae",
|
||||
"vocoder",
|
||||
"spatial_upsampler",
|
||||
"condition_image_encoder",
|
||||
}
|
||||
)
|
||||
CPU_OFFLOAD_FLAG_NAMES = (
|
||||
"dit_cpu_offload",
|
||||
"text_encoder_cpu_offload",
|
||||
"image_encoder_cpu_offload",
|
||||
"vae_cpu_offload",
|
||||
)
|
||||
|
||||
|
||||
def is_dit_component_name(component_name: str) -> bool:
|
||||
return component_name in DIT_COMPONENT_NAMES
|
||||
|
||||
|
||||
def is_text_encoder_component_name(component_name: str) -> bool:
|
||||
return component_name.startswith("text_encoder") or component_name.endswith(
|
||||
"text_encoder"
|
||||
)
|
||||
|
||||
|
||||
def is_image_encoder_component_name(component_name: str) -> bool:
|
||||
return component_name == "image_encoder"
|
||||
|
||||
|
||||
def is_vae_component_name(component_name: str) -> bool:
|
||||
return component_name in VAE_COMPONENT_NAMES
|
||||
|
||||
|
||||
def layerwise_component_matches_selection(
|
||||
component_name: str,
|
||||
selected_component_name: str,
|
||||
) -> bool:
|
||||
"""if the provided component_name (unnormalized, e.g., text_encoder_2) matches with the selected_component_name (normalized)"""
|
||||
if selected_component_name == "text_encoder":
|
||||
return is_text_encoder_component_name(component_name)
|
||||
if selected_component_name == "vae":
|
||||
return is_vae_component_name(component_name)
|
||||
return component_name == selected_component_name
|
||||
|
||||
|
||||
def cpu_offload_flags_for_layerwise_components(
|
||||
component_names: Sequence[str],
|
||||
) -> tuple[str, ...]:
|
||||
if LAYERWISE_OFFLOAD_ALL_COMPONENTS in component_names:
|
||||
return CPU_OFFLOAD_FLAG_NAMES
|
||||
|
||||
flag_names: list[str] = []
|
||||
if LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS in component_names:
|
||||
flag_names.append("dit_cpu_offload")
|
||||
|
||||
for component_name in component_names:
|
||||
if component_name == LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS:
|
||||
continue
|
||||
if is_dit_component_name(component_name):
|
||||
flag_name = "dit_cpu_offload"
|
||||
elif is_text_encoder_component_name(component_name):
|
||||
flag_name = "text_encoder_cpu_offload"
|
||||
elif is_image_encoder_component_name(component_name):
|
||||
flag_name = "image_encoder_cpu_offload"
|
||||
elif is_vae_component_name(component_name):
|
||||
flag_name = "vae_cpu_offload"
|
||||
else:
|
||||
continue
|
||||
|
||||
if flag_name not in flag_names:
|
||||
flag_names.append(flag_name)
|
||||
|
||||
return tuple(flag_names)
|
||||
|
||||
|
||||
def normalize_layerwise_offload_components(
|
||||
component_names: str | Sequence[str] | None,
|
||||
) -> list[str] | None:
|
||||
if component_names is None:
|
||||
return None
|
||||
|
||||
raw_components = (
|
||||
[component_names] if isinstance(component_names, str) else component_names
|
||||
)
|
||||
normalized_components: list[str] = []
|
||||
for raw_component in raw_components:
|
||||
if not isinstance(raw_component, str):
|
||||
raise ValueError(
|
||||
f"Invalid layerwise offload component name: {raw_component}."
|
||||
)
|
||||
for component_name in raw_component.split(","):
|
||||
component_name = component_name.strip().replace("-", "_").lower()
|
||||
if not component_name:
|
||||
continue
|
||||
if component_name == LAYERWISE_OFFLOAD_ALL_COMPONENTS:
|
||||
return [LAYERWISE_OFFLOAD_ALL_COMPONENTS]
|
||||
if component_name not in normalized_components:
|
||||
normalized_components.append(component_name)
|
||||
|
||||
return normalized_components or None
|
||||
@@ -26,7 +26,9 @@ from sglang.multimodal_gen.runtime.layers.linear import (
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -397,7 +399,7 @@ class ConditionalCrossAttentionBlock(nn.Module):
|
||||
|
||||
class DualTowerConditionalBridge(
|
||||
CachableDiT,
|
||||
OffloadableDiTMixin,
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
):
|
||||
"""Dual-tower conditional bridge module v2 (SGLang optimized version).
|
||||
|
||||
@@ -407,6 +409,8 @@ class DualTowerConditionalBridge(
|
||||
3. Cross-attention interaction between the hidden states of the two DiTs.
|
||||
"""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
|
||||
_fsdp_shard_conditions = MOVADualTowerConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = MOVADualTowerConfig()._compile_conditions
|
||||
_supported_attention_backends = MOVADualTowerConfig()._supported_attention_backends
|
||||
|
||||
@@ -13,7 +13,9 @@ from torch.nn.attention.flex_attention import (
|
||||
flex_attention,
|
||||
)
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
# wan 1.3B model has a weird channel / head configurations and require max-autotune to work with flexattention
|
||||
# see https://github.com/pytorch/pytorch/issues/133254
|
||||
@@ -58,7 +60,6 @@ logger = init_logger(__name__)
|
||||
|
||||
|
||||
class CausalWanSelfAttention(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -251,7 +252,6 @@ class CausalWanSelfAttention(nn.Module):
|
||||
|
||||
|
||||
class CausalWanTransformerBlock(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -429,7 +429,7 @@ class CausalWanTransformerBlock(nn.Module):
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CausalWanTransformer3DModel(BaseDiT, OffloadableDiTMixin):
|
||||
class CausalWanTransformer3DModel(BaseDiT, LayerwiseOffloadableModuleMixin):
|
||||
_fsdp_shard_conditions = WanVideoConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = WanVideoConfig()._compile_conditions
|
||||
_supported_attention_backends = WanVideoConfig()._supported_attention_backends
|
||||
@@ -660,10 +660,13 @@ class CausalWanTransformer3DModel(BaseDiT, OffloadableDiTMixin):
|
||||
hidden_states = self.patch_embedding(hidden_states)
|
||||
hidden_states = hidden_states.flatten(2).transpose(1, 2)
|
||||
|
||||
temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = (
|
||||
self.condition_embedder(
|
||||
timestep.flatten(), encoder_hidden_states, encoder_hidden_states_image
|
||||
)
|
||||
(
|
||||
temb,
|
||||
timestep_proj,
|
||||
encoder_hidden_states,
|
||||
encoder_hidden_states_image,
|
||||
) = self.condition_embedder(
|
||||
timestep.flatten(), encoder_hidden_states, encoder_hidden_states_image
|
||||
)
|
||||
timestep_proj = timestep_proj.unflatten(1, (6, self.hidden_size)).unflatten(
|
||||
dim=0, sizes=timestep.shape
|
||||
@@ -802,10 +805,13 @@ class CausalWanTransformer3DModel(BaseDiT, OffloadableDiTMixin):
|
||||
hidden_states = self.patch_embedding(hidden_states)
|
||||
hidden_states = hidden_states.flatten(2).transpose(1, 2)
|
||||
|
||||
temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = (
|
||||
self.condition_embedder(
|
||||
timestep.flatten(), encoder_hidden_states, encoder_hidden_states_image
|
||||
)
|
||||
(
|
||||
temb,
|
||||
timestep_proj,
|
||||
encoder_hidden_states,
|
||||
encoder_hidden_states_image,
|
||||
) = self.condition_embedder(
|
||||
timestep.flatten(), encoder_hidden_states, encoder_hidden_states_image
|
||||
)
|
||||
timestep_proj = timestep_proj.unflatten(1, (6, self.hidden_size)).unflatten(
|
||||
dim=0, sizes=timestep.shape
|
||||
|
||||
@@ -33,7 +33,9 @@ from sglang.multimodal_gen.runtime.layers.linear import (
|
||||
RowParallelLinear,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.quantization import QuantizationConfig
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
|
||||
|
||||
@@ -172,7 +174,6 @@ class ErnieImageSelfAttention(nn.Module):
|
||||
|
||||
|
||||
class ErnieImageMLP(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int,
|
||||
@@ -266,7 +267,7 @@ def _apply_rotary_bshd(x: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
|
||||
return torch.cat((x_rot, x_pass), dim=-1)
|
||||
|
||||
|
||||
class ErnieImageTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class ErnieImageTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""ErnieImage DiT: Single-stream transformer with Shared AdaLN."""
|
||||
|
||||
_supports_gradient_checkpointing = True
|
||||
|
||||
@@ -52,7 +52,9 @@ from sglang.multimodal_gen.runtime.layers.visual_embedding import (
|
||||
CombinedTimestepGuidanceTextProjEmbeddings,
|
||||
CombinedTimestepTextProjEmbeddings,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -355,9 +357,14 @@ class FluxAttention(torch.nn.Module, AttentionModuleMixin):
|
||||
freqs_cis=None,
|
||||
num_replicated_prefix: int = 0,
|
||||
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
||||
query, key, value, encoder_query, encoder_key, encoder_value = (
|
||||
_get_qkv_projections(self, x, encoder_hidden_states)
|
||||
)
|
||||
(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
encoder_query,
|
||||
encoder_key,
|
||||
encoder_value,
|
||||
) = _get_qkv_projections(self, x, encoder_hidden_states)
|
||||
|
||||
query = query.unflatten(-1, (self.heads, -1))
|
||||
key = key.unflatten(-1, (self.heads, -1))
|
||||
@@ -658,9 +665,13 @@ class FluxTransformerBlock(nn.Module):
|
||||
hidden_states, emb=temb
|
||||
)
|
||||
|
||||
norm_encoder_hidden_states, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = (
|
||||
self.norm1_context(encoder_hidden_states, emb=temb)
|
||||
)
|
||||
(
|
||||
norm_encoder_hidden_states,
|
||||
c_gate_msa,
|
||||
c_shift_mlp,
|
||||
c_scale_mlp,
|
||||
c_gate_mlp,
|
||||
) = self.norm1_context(encoder_hidden_states, emb=temb)
|
||||
|
||||
joint_attention_kwargs = joint_attention_kwargs or {}
|
||||
# Attention.
|
||||
@@ -745,7 +756,7 @@ class FluxPosEmbed(nn.Module):
|
||||
return freqs_cos.contiguous().float(), freqs_sin.contiguous().float()
|
||||
|
||||
|
||||
class FluxTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class FluxTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
The Transformer model introduced in Flux.
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
NDRotaryEmbedding,
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import (
|
||||
AttentionBackendEnum,
|
||||
@@ -294,9 +296,14 @@ class Flux2Attention(torch.nn.Module, AttentionModuleMixin):
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
||||
) -> torch.Tensor:
|
||||
query, key, value, encoder_query, encoder_key, encoder_value = (
|
||||
_get_qkv_projections(self, hidden_states, encoder_hidden_states)
|
||||
)
|
||||
(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
encoder_query,
|
||||
encoder_key,
|
||||
encoder_value,
|
||||
) = _get_qkv_projections(self, hidden_states, encoder_hidden_states)
|
||||
|
||||
query = query.unflatten(-1, (self.local_heads, -1))
|
||||
key = key.unflatten(-1, (self.local_heads, -1))
|
||||
@@ -850,7 +857,7 @@ class Flux2PosEmbed(nn.Module):
|
||||
return freqs_cos.contiguous().float(), freqs_sin.contiguous().float()
|
||||
|
||||
|
||||
class Flux2Transformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class Flux2Transformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
The Transformer model introduced in Flux 2.
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.visual_embedding import Timesteps
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import (
|
||||
AttentionBackendEnum,
|
||||
@@ -661,7 +663,7 @@ class GlmImageAdaLayerNormContinuous(nn.Module):
|
||||
return x
|
||||
|
||||
|
||||
class GlmImageTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class GlmImageTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
r"""
|
||||
Args:
|
||||
patch_size (`int`, defaults to `2`):
|
||||
|
||||
@@ -47,7 +47,9 @@ from sglang.multimodal_gen.runtime.layers.visual_embedding import (
|
||||
TimestepEmbedder,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import get_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -525,7 +527,7 @@ class HeliosTransformerBlock(nn.Module):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class HeliosTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class HeliosTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
Helios Transformer 3D model for video generation.
|
||||
|
||||
@@ -671,9 +673,13 @@ class HeliosTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
|
||||
# 1. Patch embed the noisy latents
|
||||
hidden_states = self.patch_embedding(hidden_states)
|
||||
_, _, post_patch_num_frames, post_patch_height, post_patch_width = (
|
||||
hidden_states.shape
|
||||
)
|
||||
(
|
||||
_,
|
||||
_,
|
||||
post_patch_num_frames,
|
||||
post_patch_height,
|
||||
post_patch_width,
|
||||
) = hidden_states.shape
|
||||
|
||||
if indices_hidden_states is None:
|
||||
indices_hidden_states = (
|
||||
|
||||
@@ -27,7 +27,9 @@ from sglang.multimodal_gen.runtime.layers.linear import (
|
||||
RowParallelLinear,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.mlp import MLP
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -453,7 +455,7 @@ class _FluxLastLayer(nn.Module):
|
||||
return x
|
||||
|
||||
|
||||
class Hunyuan3D2DiT(CachableDiT, OffloadableDiTMixin):
|
||||
class Hunyuan3D2DiT(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""Hunyuan3D DiT model (Flux-style architecture for Hunyuan3D-2.0)."""
|
||||
|
||||
_aliases = ["hy3dgen.shapegen.models.Hunyuan3DDiT"]
|
||||
@@ -560,7 +562,7 @@ class Hunyuan3D2DiT(CachableDiT, OffloadableDiTMixin):
|
||||
|
||||
self.final_layer = _FluxLastLayer(self.hidden_size, 1, self.out_channels)
|
||||
|
||||
# OffloadableDiTMixin
|
||||
# LayerwiseOffloadableModuleMixin
|
||||
self.layer_names = ["double_blocks", "single_blocks"]
|
||||
|
||||
def forward(
|
||||
|
||||
@@ -37,7 +37,9 @@ from sglang.multimodal_gen.runtime.layers.visual_embedding import (
|
||||
unpatchify,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import get_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.models.utils import modulate
|
||||
from sglang.multimodal_gen.runtime.platforms import (
|
||||
@@ -418,7 +420,7 @@ class MMSingleStreamBlock(nn.Module):
|
||||
return self.output_residual(output, mod_gate, x)
|
||||
|
||||
|
||||
class HunyuanVideoTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class HunyuanVideoTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
HunyuanVideo Transformer backbone adapted for distributed training.
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config impor
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import NDRotaryEmbedding
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import get_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.models.dits.wanvideo import WanTimeTextImageEmbedding
|
||||
from sglang.multimodal_gen.runtime.models.utils import set_weight_attrs
|
||||
@@ -89,7 +91,6 @@ class ModulateWan(nn.Module):
|
||||
|
||||
|
||||
class MMDoubleStreamBlock(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int,
|
||||
@@ -328,7 +329,7 @@ class MMDoubleStreamBlock(nn.Module):
|
||||
return img, txt
|
||||
|
||||
|
||||
class JoyTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class JoyTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
JoyImage Transformer 3D Model for image generation.
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config impor
|
||||
QuantizationConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.visual_embedding import timestep_embedding
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -1235,7 +1237,7 @@ class LTX2TransformerBlock(nn.Module):
|
||||
return hidden_states, audio_hidden_states
|
||||
|
||||
|
||||
class LTX2VideoTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class LTX2VideoTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_fsdp_shard_conditions = LTX2ArchConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = LTX2ArchConfig()._compile_conditions
|
||||
_supported_attention_backends = LTX2ArchConfig()._supported_attention_backends
|
||||
|
||||
@@ -18,7 +18,9 @@ from sglang.multimodal_gen.runtime.layers.mlp import MLP
|
||||
from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
|
||||
# Reuse common functions and classes from mova_video_dit
|
||||
@@ -101,7 +103,7 @@ class Conv1dLocalIsland(nn.Conv1d):
|
||||
return super().forward(input)
|
||||
|
||||
|
||||
class WanAudioModel(CachableDiT, OffloadableDiTMixin):
|
||||
class WanAudioModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_fsdp_shard_conditions = MOVAAudioConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = MOVAAudioConfig()._compile_conditions
|
||||
_supported_attention_backends = MOVAAudioConfig()._supported_attention_backends
|
||||
|
||||
@@ -33,7 +33,9 @@ from sglang.multimodal_gen.runtime.layers.mlp import MLP
|
||||
from sglang.multimodal_gen.runtime.layers.quantization.configs.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -419,7 +421,7 @@ class Conv3dLocalIsland(nn.Conv3d):
|
||||
return super().forward(input)
|
||||
|
||||
|
||||
class WanModel(CachableDiT, OffloadableDiTMixin):
|
||||
class WanModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_fsdp_shard_conditions = MOVAVideoConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = MOVAVideoConfig()._compile_conditions
|
||||
_supported_attention_backends = MOVAVideoConfig()._supported_attention_backends
|
||||
|
||||
@@ -45,7 +45,9 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config i
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -652,9 +654,14 @@ class QwenImageCrossAttention(nn.Module):
|
||||
"encoder_hidden_states_mask"
|
||||
)
|
||||
|
||||
img_query, img_key, img_value, txt_query, txt_key, txt_value = (
|
||||
_get_qkv_projections(self, hidden_states, encoder_hidden_states)
|
||||
)
|
||||
(
|
||||
img_query,
|
||||
img_key,
|
||||
img_value,
|
||||
txt_query,
|
||||
txt_key,
|
||||
txt_value,
|
||||
) = _get_qkv_projections(self, hidden_states, encoder_hidden_states)
|
||||
|
||||
# Reshape for multi-head attention
|
||||
img_query = img_query.unflatten(-1, (self.num_heads, -1))
|
||||
@@ -1118,7 +1125,7 @@ def to_hashable(obj):
|
||||
return obj
|
||||
|
||||
|
||||
class QwenImageTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class QwenImageTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
The Transformer model introduced in Qwen.
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ from diffusers.models.embeddings import PixArtAlphaTextProjection, TimestepEmbed
|
||||
from sglang.multimodal_gen.configs.models.dits.sana import SanaConfig
|
||||
from sglang.multimodal_gen.runtime.layers.layernorm import RMSNorm
|
||||
from sglang.multimodal_gen.runtime.layers.visual_embedding import Timesteps
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -239,7 +241,7 @@ class SanaTransformerBlock(nn.Module):
|
||||
return hidden_states
|
||||
|
||||
|
||||
class SanaTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class SanaTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
|
||||
_fsdp_shard_conditions = [
|
||||
lambda n, m: isinstance(m, SanaTransformerBlock),
|
||||
|
||||
@@ -17,16 +17,20 @@ from diffusers.models.normalization import AdaLayerNormContinuous
|
||||
from sglang.multimodal_gen.configs.models.dits.stablediffusion3 import (
|
||||
StableDiffusion3TransformerConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class SD3Transformer2DModel(CachableDiT):
|
||||
class SD3Transformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_supports_gradient_checkpointing = True
|
||||
_no_split_modules = ["JointTransformerBlock"]
|
||||
_skip_layerwise_casting_patterns = ["pos_embed", "norm"]
|
||||
layer_names = ["transformer_blocks"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -49,7 +49,9 @@ from sglang.multimodal_gen.runtime.layers.visual_embedding import (
|
||||
TimestepEmbedder,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import get_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.models.utils import (
|
||||
_use_aiter,
|
||||
@@ -70,7 +72,6 @@ if _use_aiter:
|
||||
|
||||
|
||||
class WanImageEmbedding(torch.nn.Module):
|
||||
|
||||
def __init__(self, in_features: int, out_features: int):
|
||||
super().__init__()
|
||||
|
||||
@@ -87,7 +88,6 @@ class WanImageEmbedding(torch.nn.Module):
|
||||
|
||||
|
||||
class WanTimeTextImageEmbedding(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -130,7 +130,6 @@ class WanTimeTextImageEmbedding(nn.Module):
|
||||
|
||||
|
||||
class WanSelfAttention(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -247,7 +246,6 @@ class WanT2VCrossAttention(WanSelfAttention):
|
||||
|
||||
|
||||
class WanI2VCrossAttention(WanSelfAttention):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -335,7 +333,6 @@ class WanI2VCrossAttention(WanSelfAttention):
|
||||
|
||||
|
||||
class WanTransformerBlock(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -514,9 +511,14 @@ class WanTransformerBlock(nn.Module):
|
||||
else:
|
||||
# temb: batch_size, 6, inner_dim (wan2.1/wan2.2 14B)
|
||||
e = self.scale_shift_table + temb.float()
|
||||
shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa = (
|
||||
e.chunk(6, dim=1)
|
||||
)
|
||||
(
|
||||
shift_msa,
|
||||
scale_msa,
|
||||
gate_msa,
|
||||
c_shift_msa,
|
||||
c_scale_msa,
|
||||
c_gate_msa,
|
||||
) = e.chunk(6, dim=1)
|
||||
|
||||
assert shift_msa.dtype == torch.float32
|
||||
|
||||
@@ -611,7 +613,6 @@ class WanTransformerBlock(nn.Module):
|
||||
|
||||
|
||||
class WanTransformerBlock_VSA(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
@@ -855,7 +856,7 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
return hidden_states
|
||||
|
||||
|
||||
class WanTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class WanTransformer3DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_fsdp_shard_conditions = WanVideoConfig()._fsdp_shard_conditions
|
||||
_compile_conditions = WanVideoConfig()._compile_conditions
|
||||
_supported_attention_backends = WanVideoConfig()._supported_attention_backends
|
||||
@@ -1088,13 +1089,16 @@ class WanTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
else:
|
||||
ts_seq_len = None
|
||||
|
||||
temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = (
|
||||
self.condition_embedder(
|
||||
timestep,
|
||||
encoder_hidden_states,
|
||||
encoder_hidden_states_image,
|
||||
timestep_seq_len=ts_seq_len,
|
||||
)
|
||||
(
|
||||
temb,
|
||||
timestep_proj,
|
||||
encoder_hidden_states,
|
||||
encoder_hidden_states_image,
|
||||
) = self.condition_embedder(
|
||||
timestep,
|
||||
encoder_hidden_states,
|
||||
encoder_hidden_states_image,
|
||||
timestep_seq_len=ts_seq_len,
|
||||
)
|
||||
if ts_seq_len is not None:
|
||||
# batch_size, seq_len, 6, inner_dim
|
||||
|
||||
@@ -41,7 +41,9 @@ from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
_apply_rotary_emb,
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import OffloadableDiTMixin
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
@@ -617,7 +619,7 @@ class RopeEmbedder:
|
||||
return torch.cat(cos_out, dim=-1), torch.cat(sin_out, dim=-1)
|
||||
|
||||
|
||||
class ZImageTransformer2DModel(CachableDiT, OffloadableDiTMixin):
|
||||
class ZImageTransformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
||||
_supports_gradient_checkpointing = True
|
||||
_no_split_modules = ["ZImageTransformerBlock"]
|
||||
_fsdp_shard_conditions = ZImageDitConfig().arch_config._fsdp_shard_conditions
|
||||
|
||||
@@ -12,10 +12,20 @@ from sglang.multimodal_gen.configs.models.encoders import (
|
||||
ImageEncoderConfig,
|
||||
TextEncoderConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
||||
|
||||
|
||||
class TextEncoder(nn.Module, ABC):
|
||||
class TextEncoder(nn.Module, ABC, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"layers",
|
||||
"encoder.block",
|
||||
"text_model.encoder.layers",
|
||||
"model.language_model.layers",
|
||||
]
|
||||
_fsdp_shard_conditions: list = field(default_factory=lambda: [])
|
||||
_stacked_params_mapping: list[tuple[str, str, str]] = field(default_factory=list)
|
||||
_supported_attention_backends: set[AttentionBackendEnum] = (
|
||||
@@ -49,7 +59,13 @@ class TextEncoder(nn.Module, ABC):
|
||||
return self._supported_attention_backends
|
||||
|
||||
|
||||
class ImageEncoder(nn.Module, ABC):
|
||||
class ImageEncoder(nn.Module, ABC, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"layers",
|
||||
"vision_model.encoder.layers",
|
||||
"model.visual.blocks",
|
||||
]
|
||||
_supported_attention_backends: set[AttentionBackendEnum] = (
|
||||
ImageEncoderConfig()._supported_attention_backends
|
||||
)
|
||||
|
||||
@@ -35,6 +35,9 @@ from sglang.multimodal_gen.runtime.layers.vocab_parallel_embedding import (
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -280,10 +283,12 @@ class Gemma2DecoderLayer(nn.Module):
|
||||
return hidden_states
|
||||
|
||||
|
||||
class Gemma2Model(nn.Module):
|
||||
class Gemma2Model(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
"""Gemma2 text encoder model for SANA pipeline."""
|
||||
|
||||
_fsdp_shard_conditions = []
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["layers"]
|
||||
|
||||
def __init__(self, config: Gemma2Config, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
@@ -24,6 +24,9 @@ from sglang.multimodal_gen.runtime.layers.linear import (
|
||||
from sglang.multimodal_gen.runtime.layers.quantization import QuantizationConfig
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import get_rope
|
||||
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.common import add_prefix
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -934,10 +937,13 @@ class Gemma3TextModel(nn.Module):
|
||||
return loaded_params
|
||||
|
||||
|
||||
class Gemma3ForConditionalGeneration(nn.Module):
|
||||
class Gemma3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
# transformers 5.6.0 flattened SiglipVisionModel, dropping the
|
||||
# `vision_model` intermediate wrapper. Our reimpl keeps it, so remap
|
||||
# HF source keys back into our nested namespace when transferring weights.
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["language_model.layers"]
|
||||
|
||||
param_names_mapping = {
|
||||
r"^(vision_tower\.)(embeddings|encoder|post_layernorm|head)\.": r"\1vision_model.\2.",
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ from transformers import (
|
||||
Dinov2Model,
|
||||
)
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
|
||||
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
||||
|
||||
@@ -28,7 +32,12 @@ def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
||||
return np.concatenate([emb_sin, emb_cos], axis=1)
|
||||
|
||||
|
||||
class ImageEncoder(nn.Module):
|
||||
class ImageEncoder(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"model.encoder.layer",
|
||||
"model.vision_model.encoder.layers",
|
||||
]
|
||||
MODEL_CLASS = None
|
||||
MODEL_CONFIG_CLASS = None
|
||||
mean = []
|
||||
@@ -203,7 +212,15 @@ def build_image_encoder(config):
|
||||
raise ValueError(f'Unknown image encoder type: {config["type"]}')
|
||||
|
||||
|
||||
class DualImageEncoder(nn.Module):
|
||||
class DualImageEncoder(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"main_image_encoder.model.encoder.layer",
|
||||
"main_image_encoder.model.vision_model.encoder.layers",
|
||||
"additional_image_encoder.model.encoder.layer",
|
||||
"additional_image_encoder.model.vision_model.encoder.layers",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
main_image_encoder,
|
||||
@@ -232,7 +249,13 @@ class DualImageEncoder(nn.Module):
|
||||
return outputs
|
||||
|
||||
|
||||
class SingleImageEncoder(nn.Module):
|
||||
class SingleImageEncoder(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"main_image_encoder.model.encoder.layer",
|
||||
"main_image_encoder.model.vision_model.encoder.layers",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
main_image_encoder,
|
||||
|
||||
@@ -41,6 +41,9 @@ from transformers.models.mistral.modeling_mistral import (
|
||||
)
|
||||
|
||||
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -370,7 +373,7 @@ class Mistral3Model(nn.Module):
|
||||
)
|
||||
|
||||
|
||||
class Mistral3ForConditionalGeneration(nn.Module):
|
||||
class Mistral3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
_checkpoint_conversion_mapping = {
|
||||
"^language_model.model": "model.language_model",
|
||||
"^multi_modal_projector": "model.multi_modal_projector",
|
||||
@@ -378,6 +381,8 @@ class Mistral3ForConditionalGeneration(nn.Module):
|
||||
}
|
||||
_tied_weights_keys = ["lm_head.weight"]
|
||||
uses_sglang_forward_context = False
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["model.language_model.layers"]
|
||||
|
||||
def __init__(self, config: LlavaConfig):
|
||||
super().__init__()
|
||||
|
||||
@@ -8,6 +8,10 @@ import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
|
||||
class BlurDownsample(torch.nn.Module):
|
||||
"""Anti-aliased spatial downsampling by integer stride using a fixed separable binomial kernel."""
|
||||
@@ -146,7 +150,7 @@ class SpatialRationalResampler(torch.nn.Module):
|
||||
return x
|
||||
|
||||
|
||||
class LatentUpsampler(torch.nn.Module):
|
||||
class LatentUpsampler(torch.nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
"""
|
||||
Upsample VAE latents spatially and/or temporally.
|
||||
|
||||
@@ -161,6 +165,9 @@ class LatentUpsampler(torch.nn.Module):
|
||||
rational_resampler: Whether to use rational resampler for spatial upsampling.
|
||||
"""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["res_blocks", "post_upsample_res_blocks"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int = 128,
|
||||
|
||||
@@ -22,9 +22,12 @@ from diffusers.models.modeling_outputs import AutoencoderKLOutput
|
||||
from torch import nn
|
||||
|
||||
from sglang.multimodal_gen.configs.models.vaes.flux import FluxVAEConfig
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
|
||||
class AutoencoderKL(nn.Module):
|
||||
class AutoencoderKL(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
r"""
|
||||
A VAE model with KL loss for encoding images into latents and decoding latent representations into images.
|
||||
|
||||
@@ -59,8 +62,10 @@ class AutoencoderKL(nn.Module):
|
||||
mid_block will only have resnet blocks
|
||||
"""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
_supports_gradient_checkpointing = True
|
||||
_no_split_modules = ["BasicTransformerBlock", "ResnetBlock2D"]
|
||||
layer_names = ["encoder.down_blocks", "decoder.up_blocks"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -6,14 +6,20 @@ import torch
|
||||
from torch import nn
|
||||
|
||||
from sglang.multimodal_gen.configs.models.vaes.sana import SanaVAEConfig
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class AutoencoderDC(nn.Module):
|
||||
class AutoencoderDC(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
"""Deep Compression Autoencoder wrapper with 32x spatial compression."""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["_inner_model.encoder.down_blocks", "_inner_model.decoder.up_blocks"]
|
||||
|
||||
def __init__(self, config: SanaVAEConfig = None, **kwargs):
|
||||
super().__init__()
|
||||
self._config = config
|
||||
|
||||
@@ -18,9 +18,19 @@ from sglang.multimodal_gen.runtime.distributed import (
|
||||
get_sp_parallel_rank,
|
||||
get_sp_world_size,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
|
||||
class ParallelTiledVAE(ABC, nn.Module):
|
||||
class ParallelTiledVAE(ABC, nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_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
|
||||
|
||||
@@ -12,6 +12,9 @@ from einops import rearrange
|
||||
from torch import nn
|
||||
|
||||
from sglang.multimodal_gen.configs.models.vaes.dac import DacVAEConfig
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vaes.common import (
|
||||
DiagonalGaussianDistribution,
|
||||
)
|
||||
@@ -413,7 +416,10 @@ class Decoder(nn.Module):
|
||||
return self.model(x)
|
||||
|
||||
|
||||
class DAC(nn.Module):
|
||||
class DAC(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["encoder.block", "decoder.model"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: DacVAEConfig,
|
||||
|
||||
@@ -12,6 +12,9 @@ import torch.nn.functional as F
|
||||
from einops import rearrange, repeat
|
||||
from tqdm import tqdm
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -1099,9 +1102,12 @@ SurfaceExtractors = {
|
||||
}
|
||||
|
||||
|
||||
class VectsetVAE(nn.Module):
|
||||
class VectsetVAE(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
"""Base VAE class for vector set encoding."""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["transformer.resblocks"]
|
||||
|
||||
def __init__(self, volume_decoder=None, surface_extractor=None):
|
||||
super().__init__()
|
||||
if volume_decoder is None:
|
||||
|
||||
@@ -3,6 +3,9 @@ from typing import Any
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vaes.ltx_2_vae import (
|
||||
LTX2VideoCausalConv3d,
|
||||
LTX2VideoResnetBlock3d,
|
||||
@@ -110,7 +113,10 @@ def _make_ltx23_encoder_block(
|
||||
)
|
||||
|
||||
|
||||
class LTX23VideoConditionEncoder(nn.Module):
|
||||
class LTX23VideoConditionEncoder(nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = ["down_blocks"]
|
||||
|
||||
def __init__(self, config: dict[str, Any]) -> None:
|
||||
super().__init__()
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from sglang.multimodal_gen.configs.models.vocoder.ltx_vocoder import LTXVocoderConfig
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
)
|
||||
|
||||
LRELU_SLOPE = 0.1
|
||||
|
||||
@@ -531,11 +534,21 @@ class LTX23VocoderCore(nn.Module):
|
||||
return x
|
||||
|
||||
|
||||
class LTX2Vocoder(ABC, nn.Module):
|
||||
class LTX2Vocoder(ABC, nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
r"""
|
||||
LTX 2.0 vocoder for converting generated mel spectrograms back to audio waveforms.
|
||||
"""
|
||||
|
||||
layerwise_offload_default_enabled = False
|
||||
layer_names = [
|
||||
"upsamplers",
|
||||
"resnets",
|
||||
"vocoder.ups",
|
||||
"vocoder.resblocks",
|
||||
"bwe_generator.ups",
|
||||
"bwe_generator.resblocks",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: LTXVocoderConfig,
|
||||
|
||||
@@ -20,7 +20,7 @@ from PIL import Image
|
||||
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.base import PipelineConfig
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentResidencyStrategy,
|
||||
get_global_component_residency_manager,
|
||||
)
|
||||
|
||||
@@ -16,12 +16,12 @@ from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader imp
|
||||
PipelineComponentLoader,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.loader.utils import BYTES_PER_GB
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentResidencyStrategy,
|
||||
ComponentUse,
|
||||
ResidencyState,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.component_resident_strategies import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_resident_strategies import (
|
||||
SnapshotModuleResidency,
|
||||
SnapshotStrategy,
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ from sglang.multimodal_gen.runtime.layers.attention.selector import (
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
|
||||
PipelineComponentLoader,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentResidencyManager,
|
||||
ComponentResidencyStrategy,
|
||||
get_global_component_residency_manager,
|
||||
|
||||
@@ -170,8 +170,8 @@ class LoRAPipeline(ComposedPipelineBase):
|
||||
Yields:
|
||||
List of modules that had offload disabled.
|
||||
"""
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import (
|
||||
OffloadableDiTMixin,
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
is_layerwise_offloaded_module,
|
||||
)
|
||||
|
||||
module_names = []
|
||||
@@ -204,10 +204,9 @@ class LoRAPipeline(ComposedPipelineBase):
|
||||
offload_disabled_modules = []
|
||||
for module_name in module_names:
|
||||
module = self.modules.get(module_name)
|
||||
if module is not None and isinstance(module, OffloadableDiTMixin):
|
||||
if module.layerwise_offload_managers is not None:
|
||||
module.disable_offload()
|
||||
offload_disabled_modules.append(module)
|
||||
if module is not None and is_layerwise_offloaded_module(module):
|
||||
module.disable_offload()
|
||||
offload_disabled_modules.append(module)
|
||||
|
||||
try:
|
||||
yield offload_disabled_modules
|
||||
|
||||
@@ -17,7 +17,9 @@ from enum import Enum, auto
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.disaggregation.roles import RoleType
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.dedup import StageDedupMixin
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
||||
|
||||
@@ -11,7 +11,9 @@ import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.vae_loader import VAELoader
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vaes.common import ParallelTiledVAE
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch, Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch, Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.decoding import DecodingStage
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
|
||||
@@ -67,8 +67,10 @@ from sglang.multimodal_gen.runtime.layers.attention.STA_configuration import (
|
||||
from sglang.multimodal_gen.runtime.loader.component_loaders.transformer_loader import (
|
||||
TransformerLoader,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
|
||||
PipelineStage,
|
||||
|
||||
@@ -2,7 +2,9 @@ import torch
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.ltx_2 import is_ltx23_native_variant
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.diffusion_scheduler_utils import (
|
||||
clone_scheduler_runtime,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,9 @@ Encoding stage for diffusion pipelines.
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vaes.common import ParallelTiledVAE
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||
|
||||
@@ -20,8 +20,13 @@ from diffusers.models.modeling_outputs import AutoencoderKLOutput
|
||||
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.base import TextConditioningOutput
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
configure_layerwise_offload_modules,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vaes.common import ParallelTiledVAE
|
||||
from sglang.multimodal_gen.runtime.models.vision_utils import (
|
||||
normalize,
|
||||
@@ -485,6 +490,14 @@ class LTX2ImageEncodingStage(PipelineStage):
|
||||
safetensors_load_file(weights_path), strict=True
|
||||
)
|
||||
self._condition_image_encoder_dir = encoder_dir
|
||||
if server_args.should_configure_layerwise_offload_for_lazy_component():
|
||||
modules = {"condition_image_encoder": self._condition_image_encoder}
|
||||
configure_layerwise_offload_modules(
|
||||
modules,
|
||||
server_args,
|
||||
component_names=server_args.layerwise_offload_components,
|
||||
warn_missing=False,
|
||||
)
|
||||
return True
|
||||
|
||||
# -- image preprocessing ---------------------------------------------
|
||||
|
||||
+3
-1
@@ -13,8 +13,10 @@ import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.diffusion_scheduler_utils import (
|
||||
get_or_create_request_scheduler,
|
||||
)
|
||||
|
||||
+3
-1
@@ -46,7 +46,9 @@ from sglang.multimodal_gen.runtime.models.dits.mova_video_dit import (
|
||||
# Create aliases for backward compatibility
|
||||
video_sinusoidal_embedding_1d = sinusoidal_embedding_1d
|
||||
audio_sinusoidal_embedding_1d = sinusoidal_embedding_1d
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch, Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import (
|
||||
PipelineStage,
|
||||
|
||||
+3
-1
@@ -8,8 +8,10 @@ from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.models.vision_utils import load_image
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||
|
||||
@@ -16,8 +16,10 @@ import torch
|
||||
from sglang.multimodal_gen.configs.models.encoders import BaseEncoderOutput
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.base import TextConditioningOutput
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.forward_context import set_forward_context
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import torch
|
||||
|
||||
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.managers.component_manager import ComponentUse
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
ComponentUse,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage
|
||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||
|
||||
@@ -26,6 +26,9 @@ logger = init_logger(__name__)
|
||||
# Default HuggingFace repo and filename for Real-ESRGAN weights
|
||||
_DEFAULT_REALESRGAN_HF_REPO = "ai-forever/Real-ESRGAN"
|
||||
_DEFAULT_REALESRGAN_FILENAME = "RealESRGAN_x4.pth"
|
||||
_LOW_MEMORY_TILED_UPSCALE_FREE_BYTES = 2 * 1024**3
|
||||
_REALESRGAN_TILE_SIZE = 256
|
||||
_REALESRGAN_TILE_PAD = 32
|
||||
|
||||
# Module-level cache: model_path -> UpscalerModel instance
|
||||
_MODEL_CACHE: dict[str, "UpscalerModel"] = {}
|
||||
@@ -263,6 +266,60 @@ class UpscalerModel:
|
||||
def device(self) -> torch.device:
|
||||
return next(self.net.parameters()).device
|
||||
|
||||
@property
|
||||
def dtype(self) -> torch.dtype:
|
||||
return next(self.net.parameters()).dtype
|
||||
|
||||
def _should_use_tiled_upscale(self, h: int, w: int) -> bool:
|
||||
if self.device.type != "cuda":
|
||||
return False
|
||||
free_bytes, _ = torch.cuda.mem_get_info(self.device)
|
||||
output_bytes = h * w * self.scale * self.scale * 3 * 4
|
||||
required_free_bytes = max(
|
||||
_LOW_MEMORY_TILED_UPSCALE_FREE_BYTES,
|
||||
output_bytes * 4,
|
||||
)
|
||||
return free_bytes < required_free_bytes
|
||||
|
||||
def _upscale_tiled_to_cpu(
|
||||
self,
|
||||
img_t: torch.Tensor,
|
||||
tile_size: int = _REALESRGAN_TILE_SIZE,
|
||||
tile_pad: int = _REALESRGAN_TILE_PAD,
|
||||
) -> torch.Tensor:
|
||||
_, channels, h, w = img_t.shape
|
||||
scale = self.scale
|
||||
output = torch.empty(
|
||||
(1, channels, h * scale, w * scale),
|
||||
dtype=torch.float32,
|
||||
device="cpu",
|
||||
)
|
||||
|
||||
for y in range(0, h, tile_size):
|
||||
tile_h = min(tile_size, h - y)
|
||||
in_y0 = max(y - tile_pad, 0)
|
||||
in_y1 = min(y + tile_h + tile_pad, h)
|
||||
out_y0 = y * scale
|
||||
out_y1 = (y + tile_h) * scale
|
||||
crop_y0 = (y - in_y0) * scale
|
||||
crop_y1 = crop_y0 + tile_h * scale
|
||||
|
||||
for x in range(0, w, tile_size):
|
||||
tile_w = min(tile_size, w - x)
|
||||
in_x0 = max(x - tile_pad, 0)
|
||||
in_x1 = min(x + tile_w + tile_pad, w)
|
||||
out_x0 = x * scale
|
||||
out_x1 = (x + tile_w) * scale
|
||||
crop_x0 = (x - in_x0) * scale
|
||||
crop_x1 = crop_x0 + tile_w * scale
|
||||
|
||||
tile = img_t[..., in_y0:in_y1, in_x0:in_x1]
|
||||
out_tile = self.net(tile)
|
||||
out_tile = out_tile[..., crop_y0:crop_y1, crop_x0:crop_x1].float()
|
||||
output[..., out_y0:out_y1, out_x0:out_x1].copy_(out_tile.cpu())
|
||||
|
||||
return output
|
||||
|
||||
def upscale(self, frame: np.ndarray, outscale: float | None = None) -> np.ndarray:
|
||||
"""Upscale a single HWC uint8 frame → HWC uint8 frame.
|
||||
|
||||
@@ -276,9 +333,34 @@ class UpscalerModel:
|
||||
"""
|
||||
h, w = frame.shape[:2]
|
||||
img = frame.astype(np.float32) / 255.0
|
||||
img_t = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0).to(self.device)
|
||||
img_t = (
|
||||
torch.from_numpy(img)
|
||||
.permute(2, 0, 1)
|
||||
.unsqueeze(0)
|
||||
.to(device=self.device, dtype=self.dtype)
|
||||
)
|
||||
with torch.no_grad():
|
||||
out = self.net(img_t)
|
||||
if self._should_use_tiled_upscale(h, w):
|
||||
logger.info(
|
||||
"Using tiled Real-ESRGAN upscale for low GPU memory: "
|
||||
"frame=%dx%d, tile_size=%d, tile_pad=%d",
|
||||
w,
|
||||
h,
|
||||
_REALESRGAN_TILE_SIZE,
|
||||
_REALESRGAN_TILE_PAD,
|
||||
)
|
||||
out = self._upscale_tiled_to_cpu(img_t)
|
||||
else:
|
||||
try:
|
||||
out = self.net(img_t)
|
||||
except torch.cuda.OutOfMemoryError:
|
||||
if self.device.type != "cuda":
|
||||
raise
|
||||
torch.cuda.empty_cache()
|
||||
logger.warning(
|
||||
"Real-ESRGAN full-frame upscale OOM; retrying with tiled upscale"
|
||||
)
|
||||
out = self._upscale_tiled_to_cpu(img_t)
|
||||
|
||||
# If the desired outscale differs from the model's native scale,
|
||||
# resize to (h * outscale, w * outscale).
|
||||
|
||||
@@ -37,6 +37,11 @@ from sglang.multimodal_gen.runtime.layers.quantization.configs.nunchaku_config i
|
||||
NunchakuConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.loader.utils import BYTES_PER_GB
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload_components import (
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS,
|
||||
cpu_offload_flags_for_layerwise_components,
|
||||
normalize_layerwise_offload_components,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import (
|
||||
AttentionBackendEnum,
|
||||
current_platform,
|
||||
@@ -194,7 +199,9 @@ class ServerArgs(DisaggArgsMixin):
|
||||
|
||||
# CPU offload parameters
|
||||
dit_cpu_offload: bool | None = None
|
||||
# if true, add the legacy default DiT components
|
||||
dit_layerwise_offload: bool | None = None
|
||||
layerwise_offload_components: list[str] | None = None
|
||||
dit_offload_prefetch_size: float = 0.0
|
||||
text_encoder_cpu_offload: bool | None = None
|
||||
image_encoder_cpu_offload: bool | None = None
|
||||
@@ -323,14 +330,15 @@ class ServerArgs(DisaggArgsMixin):
|
||||
def _adjust_parameters(self):
|
||||
"""set defaults and normalize values."""
|
||||
auto_tuner = ServerArgsAutoTuner(self)
|
||||
auto_tuner.adjust()
|
||||
auto_tuner.adjust_based_on_performance_mode()
|
||||
if auto_tuner.could_override_server_args():
|
||||
self._adjust_offload()
|
||||
auto_tuner.maybe_adjust_auto_dit_layerwise_offload()
|
||||
auto_tuner.maybe_adjust_auto_default_layerwise_offload()
|
||||
self._adjust_ltx2_two_stage_device_mode()
|
||||
if auto_tuner.could_override_server_args():
|
||||
auto_tuner.maybe_adjust_auto_component_residency_after_offload()
|
||||
auto_tuner.maybe_adjust_auto_fsdp_with_offload_enabled()
|
||||
auto_tuner.maybe_replace_cpu_offloaded_components_with_layerwise()
|
||||
self._adjust_path()
|
||||
self._adjust_quant_config()
|
||||
self._adjust_warmup()
|
||||
@@ -339,6 +347,7 @@ class ServerArgs(DisaggArgsMixin):
|
||||
self._adjust_parallelism()
|
||||
self._adjust_attention_backend()
|
||||
self._adjust_platform_specific()
|
||||
self._adjust_layerwise_offload_components()
|
||||
self._adjust_autocast()
|
||||
auto_tuner.finalize_auto_flags()
|
||||
self.adjust_pipeline_config()
|
||||
@@ -802,6 +811,82 @@ class ServerArgs(DisaggArgsMixin):
|
||||
if current_platform.is_mps():
|
||||
self.use_fsdp_inference = False
|
||||
self.dit_layerwise_offload = False
|
||||
self.layerwise_offload_components = None
|
||||
|
||||
def should_configure_layerwise_offload_for_lazy_component(self) -> bool:
|
||||
"""Return whether a lazy-loaded component should try layerwise offload.
|
||||
|
||||
Lazy components are loaded after the normal pipeline-wide configuration
|
||||
pass, so they should only attempt layerwise configuration when a
|
||||
component scope is present.
|
||||
"""
|
||||
return bool(self.layerwise_offload_components)
|
||||
|
||||
@property
|
||||
def is_dit_layerwise_offload_selected(self) -> bool:
|
||||
"""returns if dit is selected to be layerwise-offload"""
|
||||
component_names = self.layerwise_offload_components
|
||||
return bool(
|
||||
component_names
|
||||
and "dit_cpu_offload"
|
||||
in cpu_offload_flags_for_layerwise_components(component_names)
|
||||
)
|
||||
|
||||
def _adjust_layerwise_offload_components(self):
|
||||
explicitly_set_component_names = normalize_layerwise_offload_components(
|
||||
self.layerwise_offload_components
|
||||
)
|
||||
if self.dit_layerwise_offload:
|
||||
if explicitly_set_component_names is None:
|
||||
explicitly_set_component_names = [LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS]
|
||||
elif (
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS
|
||||
not in explicitly_set_component_names
|
||||
):
|
||||
explicitly_set_component_names = [
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS,
|
||||
*explicitly_set_component_names,
|
||||
]
|
||||
|
||||
if explicitly_set_component_names is not None:
|
||||
self.layerwise_offload_components = explicitly_set_component_names
|
||||
self._disable_cpu_offload_for_layerwise_components(
|
||||
explicitly_set_component_names
|
||||
)
|
||||
return
|
||||
|
||||
def _disable_cpu_offload_for_layerwise_components(
|
||||
self, component_names: list[str]
|
||||
) -> None:
|
||||
# Layerwise offload owns H2D/D2H for selected component weights.
|
||||
flag_names = cpu_offload_flags_for_layerwise_components(component_names)
|
||||
disabled_flag_names: list[str] = []
|
||||
|
||||
if "dit_cpu_offload" in flag_names and self.dit_cpu_offload is not False:
|
||||
self.dit_cpu_offload = False
|
||||
disabled_flag_names.append("dit_cpu_offload")
|
||||
if (
|
||||
"text_encoder_cpu_offload" in flag_names
|
||||
and self.text_encoder_cpu_offload is not False
|
||||
):
|
||||
self.text_encoder_cpu_offload = False
|
||||
disabled_flag_names.append("text_encoder_cpu_offload")
|
||||
if (
|
||||
"image_encoder_cpu_offload" in flag_names
|
||||
and self.image_encoder_cpu_offload is not False
|
||||
):
|
||||
self.image_encoder_cpu_offload = False
|
||||
disabled_flag_names.append("image_encoder_cpu_offload")
|
||||
if "vae_cpu_offload" in flag_names and self.vae_cpu_offload is not False:
|
||||
self.vae_cpu_offload = False
|
||||
disabled_flag_names.append("vae_cpu_offload")
|
||||
|
||||
if disabled_flag_names:
|
||||
logger.info(
|
||||
"Disabling %s because the selected layerwise offload components "
|
||||
"manage the same weights.",
|
||||
", ".join(disabled_flag_names),
|
||||
)
|
||||
|
||||
def _adjust_autocast(self):
|
||||
if self.disable_autocast is None:
|
||||
@@ -944,13 +1029,6 @@ class ServerArgs(DisaggArgsMixin):
|
||||
help="The specific model version to use (can be a branch name, tag name, or commit id)",
|
||||
)
|
||||
|
||||
# Parallelism
|
||||
parser.add_argument(
|
||||
"--num-gpus",
|
||||
type=int,
|
||||
default=ServerArgs.num_gpus,
|
||||
help="The number of GPUs to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--performance-mode",
|
||||
"--mode",
|
||||
@@ -959,7 +1037,7 @@ class ServerArgs(DisaggArgsMixin):
|
||||
default=ServerArgs.performance_mode,
|
||||
help=(
|
||||
"Preset for performance and memory defaults. "
|
||||
"'manual' keeps performance-related server args under explicit user control; "
|
||||
"'manual' keeps performance-related server args under explicit user control, no adjustment is made; "
|
||||
"'auto' keeps safe defaults and applies high-confidence FSDP/CFG improvements; "
|
||||
"'speed' favors GPU-resident execution for lower latency and higher throughput, and may OOM; "
|
||||
"'memory' favors lower GPU memory usage; "
|
||||
@@ -967,6 +1045,13 @@ class ServerArgs(DisaggArgsMixin):
|
||||
),
|
||||
)
|
||||
|
||||
# Parallelism
|
||||
parser.add_argument(
|
||||
"--num-gpus",
|
||||
type=int,
|
||||
default=ServerArgs.num_gpus,
|
||||
help="The number of GPUs to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tp-size",
|
||||
type=int,
|
||||
@@ -1093,8 +1178,21 @@ class ServerArgs(DisaggArgsMixin):
|
||||
"--dit-layerwise-offload",
|
||||
action=StoreBoolean,
|
||||
default=ServerArgs.dit_layerwise_offload,
|
||||
help="Enable layerwise CPU offload with async H2D prefetch overlap for supported DiT models (e.g., Wan, MOVA). "
|
||||
"Cannot be used together with cache-dit (SGLANG_CACHE_DIT_ENABLED), dit_cpu_offload, or use_fsdp_inference.",
|
||||
help="Enable layerwise CPU offload with async H2D prefetch overlap for DiTs. "
|
||||
"It only selects the legacy default DiT components. Cannot be used together with cache-dit "
|
||||
"(SGLANG_CACHE_DIT_ENABLED), dit_cpu_offload, or use_fsdp_inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--layerwise-offload-components",
|
||||
"--layerwise-offload-modules",
|
||||
type=str,
|
||||
nargs="+",
|
||||
default=ServerArgs.layerwise_offload_components,
|
||||
help="Select pipeline components for layerwise offload. "
|
||||
"Use default to select the legacy default DiT components, "
|
||||
"or all to select every layerwise-offloadable component. "
|
||||
"This option does not imply --dit-layerwise-offload. Example: "
|
||||
"--layerwise-offload-components text_encoder image_encoder.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dit-offload-prefetch-size",
|
||||
@@ -1520,7 +1618,7 @@ class ServerArgs(DisaggArgsMixin):
|
||||
@classmethod
|
||||
def from_dict(cls, kwargs: dict[str, Any]) -> "ServerArgs":
|
||||
"""Create a ServerArgs object from a dictionary."""
|
||||
attrs = [attr.name for attr in dataclasses.fields(cls)]
|
||||
attrs = [attr.name for attr in dataclasses.fields(cls) if attr.init]
|
||||
server_args_kwargs: dict[str, Any] = {}
|
||||
|
||||
component_paths = dict(kwargs.get("component_paths") or {})
|
||||
@@ -1623,34 +1721,36 @@ class ServerArgs(DisaggArgsMixin):
|
||||
"We do not recommend --dit-offload-prefetch-size to be between 0.5 and 1.0"
|
||||
)
|
||||
|
||||
# validate dit_layerwise_offload conflicts
|
||||
if self.dit_layerwise_offload:
|
||||
# validate layerwise offload conflicts
|
||||
if self.layerwise_offload_components:
|
||||
if self.dit_offload_prefetch_size < 0.0:
|
||||
raise ValueError("dit_offload_prefetch_size must be non-negative")
|
||||
|
||||
if self.use_fsdp_inference:
|
||||
logger.warning(
|
||||
"dit_layerwise_offload is enabled, automatically disabling use_fsdp_inference."
|
||||
"layerwise offload components are selected, automatically disabling use_fsdp_inference."
|
||||
)
|
||||
self.use_fsdp_inference = False
|
||||
|
||||
if self.dit_cpu_offload is None:
|
||||
should_disable_dit_cpu_offload = self.is_dit_layerwise_offload_selected
|
||||
if should_disable_dit_cpu_offload and self.dit_cpu_offload is not False:
|
||||
logger.warning(
|
||||
"dit_layerwise_offload is enabled, automatically disabling dit_cpu_offload."
|
||||
"layerwise offload is selected for DiT components, automatically disabling dit_cpu_offload."
|
||||
)
|
||||
self.dit_cpu_offload = False
|
||||
|
||||
if envs.SGLANG_CACHE_DIT_ENABLED:
|
||||
if envs.SGLANG_CACHE_DIT_ENABLED and should_disable_dit_cpu_offload:
|
||||
raise ValueError(
|
||||
"dit_layerwise_offload cannot be enabled together with cache-dit. "
|
||||
"DiT layerwise offload cannot be enabled together with cache-dit. "
|
||||
"cache-dit may reuse skipped blocks whose weights have been released by layerwise offload, "
|
||||
"causing shape mismatch errors. "
|
||||
"Please disable either --dit-layerwise-offload or SGLANG_CACHE_DIT_ENABLED."
|
||||
"Please disable --dit-layerwise-offload, remove DiT from --layerwise-offload-components, "
|
||||
"or disable SGLANG_CACHE_DIT_ENABLED."
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
"dit_layerwise_offload is enabled: %slower GPU memory usage%s, but %smay reduce throughput or increase latency%s. "
|
||||
"%sIf you are using multi-GPU deployment and already have enough memory headroom, prefer keeping dit_layerwise_offload disabled.%s "
|
||||
"layerwise offload components are selected: %slower GPU memory usage%s, but %smay reduce throughput or increase latency%s. "
|
||||
"%sIf you are using multi-GPU deployment and already have enough memory headroom, prefer keeping layerwise offload disabled.%s "
|
||||
"Please tune this based on your memory headroom and performance target.",
|
||||
GREEN,
|
||||
RESET,
|
||||
|
||||
@@ -10,6 +10,9 @@ from sglang.multimodal_gen import envs
|
||||
from sglang.multimodal_gen.configs.pipeline_configs.model_deployment_config import (
|
||||
ModelDeploymentConfig,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload_components import (
|
||||
LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||
|
||||
@@ -27,11 +30,14 @@ class ServerArgsAutoTuner:
|
||||
def __init__(self, server_args: "ServerArgs"):
|
||||
self.server_args = server_args
|
||||
self._explicit_memory_policy = self._has_explicit_memory_policy()
|
||||
self._explicit_layerwise_replacement_policy = (
|
||||
self._has_explicit_layerwise_replacement_policy()
|
||||
)
|
||||
|
||||
def _deployment_config(self) -> ModelDeploymentConfig:
|
||||
return self.server_args.pipeline_config.get_model_deployment_config()
|
||||
|
||||
def adjust(self) -> None:
|
||||
def adjust_based_on_performance_mode(self) -> None:
|
||||
"""Adjust the server args based on the performance mode"""
|
||||
args = self.server_args
|
||||
args.performance_mode = self._normalize_performance_mode()
|
||||
@@ -56,8 +62,8 @@ class ServerArgsAutoTuner:
|
||||
self._set_gpu_resident_defaults(use_fsdp=True)
|
||||
return
|
||||
args.use_fsdp_inference = False
|
||||
if self._can_apply_dit_layerwise_offload_policy():
|
||||
# apply dit layerwise offload to save VRAM during denoising stage
|
||||
if self._can_apply_default_layerwise_offload_policy():
|
||||
# apply default layerwise offload to save VRAM during denoising stage
|
||||
self._set_layerwise_offload_defaults()
|
||||
else:
|
||||
self._set_component_offload_defaults()
|
||||
@@ -127,7 +133,8 @@ class ServerArgsAutoTuner:
|
||||
args.dit_layerwise_offload = False
|
||||
self._enable_cfg_parallel_if_supported()
|
||||
|
||||
def maybe_adjust_auto_dit_layerwise_offload(self) -> None:
|
||||
def maybe_adjust_auto_default_layerwise_offload(self) -> None:
|
||||
"""adjust the default layerwise offload policy"""
|
||||
args = self.server_args
|
||||
if not self.could_override_server_args():
|
||||
return
|
||||
@@ -178,6 +185,43 @@ class ServerArgsAutoTuner:
|
||||
args.dit_layerwise_offload = True
|
||||
args.dit_cpu_offload = False
|
||||
|
||||
def maybe_replace_cpu_offloaded_components_with_layerwise(self) -> None:
|
||||
args = self.server_args
|
||||
if (
|
||||
not self.could_override_server_args()
|
||||
or self._explicit_layerwise_replacement_policy
|
||||
or current_platform.is_cpu()
|
||||
or not current_platform.is_cuda()
|
||||
or envs.SGLANG_CACHE_DIT_ENABLED
|
||||
or args.use_fsdp_inference
|
||||
or args.layerwise_offload_components is not None
|
||||
):
|
||||
return
|
||||
|
||||
layerwise_components: list[str] = []
|
||||
if args.dit_layerwise_offload:
|
||||
layerwise_components.append(LAYERWISE_OFFLOAD_DEFAULT_COMPONENTS)
|
||||
|
||||
changed: list[str] = []
|
||||
if args.text_encoder_cpu_offload:
|
||||
layerwise_components.append("text_encoder")
|
||||
changed.append("text_encoder")
|
||||
if args.image_encoder_cpu_offload:
|
||||
layerwise_components.append("image_encoder")
|
||||
changed.append("image_encoder")
|
||||
if args.vae_cpu_offload:
|
||||
layerwise_components.append("vae")
|
||||
changed.append("vae")
|
||||
|
||||
if not changed:
|
||||
return
|
||||
|
||||
args.layerwise_offload_components = layerwise_components
|
||||
logger.info(
|
||||
"Automatically replacing CPU offload with layerwise offload for components: %s",
|
||||
", ".join(changed),
|
||||
)
|
||||
|
||||
def finalize_auto_flags(self) -> None:
|
||||
"""if some args are unset after all the adjustment, set them to defaults"""
|
||||
if not self.could_override_server_args():
|
||||
@@ -266,7 +310,7 @@ class ServerArgsAutoTuner:
|
||||
if args.image_encoder_cpu_offload is None:
|
||||
args.image_encoder_cpu_offload = True
|
||||
|
||||
def _can_apply_dit_layerwise_offload_policy(self) -> bool:
|
||||
def _can_apply_default_layerwise_offload_policy(self) -> bool:
|
||||
return (
|
||||
self._deployment_config().auto_dit_layerwise_offload
|
||||
and not envs.SGLANG_CACHE_DIT_ENABLED
|
||||
@@ -299,10 +343,21 @@ class ServerArgsAutoTuner:
|
||||
args.use_fsdp_inference is not None
|
||||
or args.dit_cpu_offload is not None
|
||||
or args.dit_layerwise_offload is not None
|
||||
or args.layerwise_offload_components is not None
|
||||
or args.text_encoder_cpu_offload is not None
|
||||
or args.image_encoder_cpu_offload is not None
|
||||
)
|
||||
|
||||
def _has_explicit_layerwise_replacement_policy(self) -> bool:
|
||||
args = self.server_args
|
||||
return (
|
||||
args.dit_layerwise_offload is not None
|
||||
or args.layerwise_offload_components is not None
|
||||
or args.text_encoder_cpu_offload is not None
|
||||
or args.image_encoder_cpu_offload is not None
|
||||
or args.vae_cpu_offload is True
|
||||
)
|
||||
|
||||
def _has_explicit_parallel_policy(self) -> bool:
|
||||
args = self.server_args
|
||||
return (
|
||||
|
||||
@@ -801,7 +801,10 @@ def get_clip_model() -> tuple[Any, Any]:
|
||||
)
|
||||
model = CLIPModel.from_pretrained(CLIP_MODEL_NAME)
|
||||
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
# ci server tests keep the generation server alive while consistency runs
|
||||
device = (
|
||||
"cpu" if is_in_ci() else ("cuda" if torch.cuda.is_available() else "cpu")
|
||||
)
|
||||
model = model.to(device)
|
||||
model.eval()
|
||||
|
||||
|
||||
@@ -9,11 +9,22 @@ from sglang.multimodal_gen.runtime.layers.quantization.modelopt_quant import (
|
||||
from sglang.multimodal_gen.runtime.loader.transformer_load_utils import (
|
||||
_ModelOptFp8OffloadAdapter,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers import (
|
||||
layerwise_offload as layerwise_offload_mod,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.layerwise_offload import (
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_manager import (
|
||||
build_component_residency_strategy,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.component_resident_strategies import (
|
||||
LayerwiseOffloadStrategy,
|
||||
ResidentStrategy,
|
||||
VanillaD2HStrategy,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||
LayerwiseOffloadableModuleMixin,
|
||||
LayerwiseOffloadManager,
|
||||
configure_layerwise_offload_modules,
|
||||
is_layerwise_offloaded_module,
|
||||
)
|
||||
|
||||
|
||||
@@ -65,6 +76,65 @@ class _DummyModel(torch.nn.Module):
|
||||
self.blocks = torch.nn.ModuleList([_DummyBlock()])
|
||||
|
||||
|
||||
class _NestedDummyModel(torch.nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layer_names = ["encoder.blocks"]
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.encoder = _DummyModel()
|
||||
|
||||
|
||||
class _SharedBuffer(torch.nn.Module):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.register_buffer(
|
||||
"cache", torch.arange(12, dtype=torch.float32).reshape(6, 2)
|
||||
)
|
||||
|
||||
|
||||
class _SharedBufferLayer(torch.nn.Module):
|
||||
def __init__(self, shared: _SharedBuffer) -> None:
|
||||
super().__init__()
|
||||
self.shared = shared
|
||||
self.weight = torch.nn.Parameter(torch.ones(2, 2, dtype=torch.float32))
|
||||
|
||||
|
||||
class _SharedBufferModel(torch.nn.Module):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
shared = _SharedBuffer()
|
||||
self.blocks = torch.nn.ModuleList(
|
||||
[_SharedBufferLayer(shared), _SharedBufferLayer(shared)]
|
||||
)
|
||||
|
||||
|
||||
class _NestedEncoderDummyModel(_NestedDummyModel):
|
||||
layerwise_offload_default_enabled = False
|
||||
|
||||
|
||||
class _LayerwiseComponent(torch.nn.Module, LayerwiseOffloadableModuleMixin):
|
||||
layer_names = ["blocks"]
|
||||
|
||||
def __init__(self, enabled: bool) -> None:
|
||||
super().__init__()
|
||||
self.blocks = torch.nn.ModuleList([_DummyBlock()])
|
||||
self.layerwise_offload_managers = [SimpleNamespace(enabled=enabled)]
|
||||
|
||||
|
||||
def _server_args(**kwargs):
|
||||
defaults = dict(
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
image_encoder_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
dit_offload_prefetch_size=1,
|
||||
pin_cpu_memory=False,
|
||||
)
|
||||
defaults.update(kwargs)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
|
||||
def test_layerwise_offload_preserves_non_contiguous_stride(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
@@ -103,6 +173,36 @@ def test_layerwise_offload_preserves_non_contiguous_stride(monkeypatch):
|
||||
assert torch.equal(reloaded_weight, original_weight)
|
||||
|
||||
|
||||
def test_layerwise_offload_keeps_shared_buffers_resident(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
)
|
||||
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
|
||||
|
||||
model = _SharedBufferModel()
|
||||
original_cache = model.blocks[0].shared.cache.detach().clone()
|
||||
|
||||
manager = LayerwiseOffloadManager(
|
||||
model=model,
|
||||
layers_attr_str="blocks",
|
||||
num_layers=2,
|
||||
enabled=True,
|
||||
pin_cpu_memory=False,
|
||||
prefetch_size=1,
|
||||
)
|
||||
|
||||
assert not any(
|
||||
"cache" in name
|
||||
for metadata in manager._weight_metadata.values()
|
||||
for name in metadata
|
||||
)
|
||||
manager.release_layer(0)
|
||||
|
||||
cache = model.blocks[1].shared.cache
|
||||
assert torch.equal(cache, original_cache)
|
||||
assert torch.equal(cache.index_select(0, torch.tensor([2])), original_cache[2:3])
|
||||
|
||||
|
||||
def test_modelopt_fp8_adapter_keeps_layerwise_offload_enabled():
|
||||
server_args = SimpleNamespace(
|
||||
dit_cpu_offload=True,
|
||||
@@ -119,6 +219,133 @@ def test_modelopt_fp8_adapter_keeps_layerwise_offload_enabled():
|
||||
assert server_args.dit_layerwise_offload is True
|
||||
|
||||
|
||||
def test_layerwise_capability_selects_layerwise_strategy_for_any_component():
|
||||
module = _LayerwiseComponent(enabled=True)
|
||||
|
||||
assert is_layerwise_offloaded_module(module)
|
||||
strategy = build_component_residency_strategy(
|
||||
"text_encoder", module, _server_args(text_encoder_cpu_offload=True)
|
||||
)
|
||||
|
||||
assert isinstance(strategy, LayerwiseOffloadStrategy)
|
||||
|
||||
|
||||
def test_layerwise_configuration_uses_legacy_default_components(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
)
|
||||
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
|
||||
layerwise_module = _NestedDummyModel()
|
||||
modules = {
|
||||
"text_encoder": layerwise_module,
|
||||
"text_encoder_alias": layerwise_module,
|
||||
"scheduler": object(),
|
||||
}
|
||||
|
||||
configured = configure_layerwise_offload_modules(modules, _server_args())
|
||||
|
||||
assert configured == ["text_encoder"]
|
||||
assert is_layerwise_offloaded_module(layerwise_module)
|
||||
|
||||
|
||||
def test_layerwise_configuration_filters_by_component_name(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
)
|
||||
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
|
||||
text_encoder = _NestedEncoderDummyModel()
|
||||
transformer = _NestedDummyModel()
|
||||
vae = _NestedDummyModel()
|
||||
modules = {
|
||||
"custom_encoder_name": text_encoder,
|
||||
"custom_transformer_name": transformer,
|
||||
"custom_vae_name": vae,
|
||||
}
|
||||
|
||||
configured = configure_layerwise_offload_modules(
|
||||
modules, _server_args(), component_names=["custom_encoder_name"]
|
||||
)
|
||||
|
||||
assert configured == ["custom_encoder_name"]
|
||||
assert is_layerwise_offloaded_module(text_encoder)
|
||||
assert not is_layerwise_offloaded_module(transformer)
|
||||
assert not is_layerwise_offloaded_module(vae)
|
||||
|
||||
|
||||
def test_layerwise_configuration_default_marker_extends_legacy_defaults(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
)
|
||||
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
|
||||
text_encoder = _NestedEncoderDummyModel()
|
||||
text_encoder_2 = _NestedEncoderDummyModel()
|
||||
transformer = _NestedDummyModel()
|
||||
vae = _NestedEncoderDummyModel()
|
||||
audio_vae = _NestedEncoderDummyModel()
|
||||
condition_image_encoder = _NestedEncoderDummyModel()
|
||||
modules = {
|
||||
"text_encoder": text_encoder,
|
||||
"text_encoder_2": text_encoder_2,
|
||||
"transformer": transformer,
|
||||
"vae": vae,
|
||||
"audio_vae": audio_vae,
|
||||
"condition_image_encoder": condition_image_encoder,
|
||||
}
|
||||
|
||||
configured = configure_layerwise_offload_modules(
|
||||
modules, _server_args(), component_names=["default", "text_encoder", "vae"]
|
||||
)
|
||||
|
||||
assert configured == [
|
||||
"text_encoder",
|
||||
"text_encoder_2",
|
||||
"transformer",
|
||||
"vae",
|
||||
"audio_vae",
|
||||
"condition_image_encoder",
|
||||
]
|
||||
assert is_layerwise_offloaded_module(text_encoder)
|
||||
assert is_layerwise_offloaded_module(text_encoder_2)
|
||||
assert is_layerwise_offloaded_module(transformer)
|
||||
assert is_layerwise_offloaded_module(vae)
|
||||
assert is_layerwise_offloaded_module(audio_vae)
|
||||
assert is_layerwise_offloaded_module(condition_image_encoder)
|
||||
|
||||
|
||||
def test_layerwise_configuration_all_selects_every_capable_component(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
)
|
||||
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
|
||||
text_encoder = _NestedEncoderDummyModel()
|
||||
transformer = _NestedDummyModel()
|
||||
modules = {
|
||||
"custom_encoder_name": text_encoder,
|
||||
"custom_transformer_name": transformer,
|
||||
"scheduler": object(),
|
||||
}
|
||||
|
||||
configured = configure_layerwise_offload_modules(
|
||||
modules, _server_args(), component_names=["all"]
|
||||
)
|
||||
|
||||
assert configured == ["custom_encoder_name", "custom_transformer_name"]
|
||||
assert is_layerwise_offloaded_module(text_encoder)
|
||||
assert is_layerwise_offloaded_module(transformer)
|
||||
|
||||
|
||||
def test_component_cpu_offload_strategy_remains_flag_driven():
|
||||
strategy = build_component_residency_strategy(
|
||||
"text_encoder", _DummyModel(), _server_args(text_encoder_cpu_offload=True)
|
||||
)
|
||||
assert isinstance(strategy, VanillaD2HStrategy)
|
||||
|
||||
strategy = build_component_residency_strategy(
|
||||
"unknown_component", _DummyModel(), _server_args(text_encoder_cpu_offload=True)
|
||||
)
|
||||
assert isinstance(strategy, ResidentStrategy)
|
||||
|
||||
|
||||
def test_layerwise_offload_aligns_contiguous_tensor_offsets(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
|
||||
|
||||
@@ -147,6 +147,106 @@ class TestServerArgsPathExpansion(unittest.TestCase):
|
||||
server_args.component_attention_backends, {"text_encoder": "torch_sdpa"}
|
||||
)
|
||||
|
||||
def test_layerwise_offload_components_imply_layerwise(self):
|
||||
args = self._from_dict_without_model_resolution(
|
||||
{
|
||||
"model_path": "/data/my-model",
|
||||
"performance_mode": "manual",
|
||||
}
|
||||
)
|
||||
args.layerwise_offload_components = ["text_encoder", "transformer"]
|
||||
args._adjust_layerwise_offload_components()
|
||||
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components, ["text_encoder", "transformer"]
|
||||
)
|
||||
|
||||
def test_dit_layerwise_offload_extends_default_components(self):
|
||||
args = self._from_dict_without_model_resolution(
|
||||
{
|
||||
"model_path": "/data/my-model",
|
||||
"performance_mode": "manual",
|
||||
"dit_layerwise_offload": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertEqual(args.layerwise_offload_components, ["default"])
|
||||
|
||||
def test_dit_layerwise_offload_from_kwargs(self):
|
||||
with patch.object(
|
||||
PipelineConfig, "from_kwargs", return_value=QwenImagePipelineConfig()
|
||||
):
|
||||
args = ServerArgs.from_kwargs(
|
||||
model_path="/data/my-model",
|
||||
performance_mode="manual",
|
||||
dit_layerwise_offload=True,
|
||||
)
|
||||
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertEqual(args.layerwise_offload_components, ["default"])
|
||||
|
||||
def test_layerwise_offload_components_normalize_commas(self):
|
||||
args = self._from_dict_without_model_resolution(
|
||||
{
|
||||
"model_path": "/data/my-model",
|
||||
"performance_mode": "manual",
|
||||
}
|
||||
)
|
||||
args.layerwise_offload_components = ["text-encoder,transformer"]
|
||||
args._adjust_layerwise_offload_components()
|
||||
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components, ["text_encoder", "transformer"]
|
||||
)
|
||||
|
||||
def test_dit_layerwise_offload_cli_arg(self):
|
||||
parser = FlexibleArgumentParser()
|
||||
ServerArgs.add_cli_args(parser)
|
||||
argv = [
|
||||
"--model-path",
|
||||
"/fake",
|
||||
"--performance-mode",
|
||||
"manual",
|
||||
"--dit-layerwise-offload",
|
||||
"true",
|
||||
]
|
||||
|
||||
with patch.object(sys, "argv", ["sglang"] + argv):
|
||||
args, unknown_args = parser.parse_known_args(argv)
|
||||
with patch.object(
|
||||
PipelineConfig, "from_kwargs", return_value=QwenImagePipelineConfig()
|
||||
):
|
||||
server_args = ServerArgs.from_cli_args(args, unknown_args)
|
||||
|
||||
self.assertTrue(server_args.layerwise_offload_components)
|
||||
self.assertEqual(server_args.layerwise_offload_components, ["default"])
|
||||
|
||||
def test_layerwise_offload_components_cli_args(self):
|
||||
parser = FlexibleArgumentParser()
|
||||
ServerArgs.add_cli_args(parser)
|
||||
argv = [
|
||||
"--model-path",
|
||||
"/fake",
|
||||
"--performance-mode",
|
||||
"manual",
|
||||
"--layerwise-offload-components",
|
||||
"transformer",
|
||||
"text_encoder",
|
||||
]
|
||||
|
||||
with patch.object(sys, "argv", ["sglang"] + argv):
|
||||
args, unknown_args = parser.parse_known_args(argv)
|
||||
with patch.object(
|
||||
PipelineConfig, "from_kwargs", return_value=QwenImagePipelineConfig()
|
||||
):
|
||||
server_args = ServerArgs.from_cli_args(args, unknown_args)
|
||||
|
||||
self.assertEqual(
|
||||
server_args.layerwise_offload_components, ["transformer", "text_encoder"]
|
||||
)
|
||||
|
||||
|
||||
class TestOffloadDefaults(unittest.TestCase):
|
||||
def _from_dict_with_pipeline_config(
|
||||
@@ -208,6 +308,10 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
"sglang.multimodal_gen.runtime.server_args.current_platform.is_cpu",
|
||||
return_value=False,
|
||||
),
|
||||
patch(
|
||||
"sglang.multimodal_gen.runtime.server_args.current_platform.is_cuda",
|
||||
return_value=True,
|
||||
),
|
||||
patch(
|
||||
"sglang.multimodal_gen.runtime.server_args.current_platform.get_device_total_memory",
|
||||
return_value=memory_gb * 1024**3,
|
||||
@@ -233,16 +337,48 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
|
||||
self.assertFalse(args.vae_cpu_offload)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.image_encoder_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components, ["text_encoder", "image_encoder"]
|
||||
)
|
||||
|
||||
def test_explicit_vae_cpu_offload_true_is_preserved(self):
|
||||
def test_explicit_vae_cpu_offload_true_is_preserved_without_component_selection(
|
||||
self,
|
||||
):
|
||||
args = self._from_dict_with_task_type(
|
||||
ModelTaskType.T2V,
|
||||
kwargs={"vae_cpu_offload": True},
|
||||
)
|
||||
|
||||
self.assertTrue(args.vae_cpu_offload)
|
||||
self.assertFalse(args.layerwise_offload_components)
|
||||
|
||||
def test_layerwise_components_disable_matching_cpu_offloads(self):
|
||||
args = self._from_dict_with_task_type(
|
||||
ModelTaskType.T2V,
|
||||
memory_gb=16,
|
||||
kwargs={
|
||||
"performance_mode": "manual",
|
||||
"dit_cpu_offload": True,
|
||||
"text_encoder_cpu_offload": True,
|
||||
"image_encoder_cpu_offload": True,
|
||||
"vae_cpu_offload": True,
|
||||
},
|
||||
)
|
||||
args.layerwise_offload_components = [
|
||||
"text_encoder",
|
||||
"image_encoder",
|
||||
"video_dit",
|
||||
"vae",
|
||||
]
|
||||
args._adjust_layerwise_offload_components()
|
||||
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertFalse(args.vae_cpu_offload)
|
||||
|
||||
def test_pipeline_configs_declare_auto_tune_hints(self):
|
||||
qwen_deployment = QwenImagePipelineConfig().get_model_deployment_config()
|
||||
@@ -285,11 +421,12 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertIsNone(args.use_fsdp_inference)
|
||||
self.assertIsNone(args.dit_cpu_offload)
|
||||
self.assertIsNone(args.dit_layerwise_offload)
|
||||
self.assertIsNone(args.layerwise_offload_components)
|
||||
self.assertIsNone(args.text_encoder_cpu_offload)
|
||||
self.assertIsNone(args.image_encoder_cpu_offload)
|
||||
self.assertFalse(args.enable_cfg_parallel)
|
||||
|
||||
def test_default_auto_keeps_legacy_single_gpu_offload_defaults(self):
|
||||
def test_default_auto_replaces_text_encoder_cpu_offload_with_layerwise(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
QwenImagePipelineConfig(),
|
||||
kwargs={"model_path": "Qwen/Qwen-Image"},
|
||||
@@ -298,11 +435,14 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertEqual(args.performance_mode, "auto")
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertFalse(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["text_encoder"])
|
||||
|
||||
def test_auto_ltx_snapshot_keeps_dit_offload_with_headroom(self):
|
||||
def test_auto_ltx_snapshot_keeps_dit_offload_and_replaces_encoder_cpu_offload(
|
||||
self,
|
||||
):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
LTX2PipelineConfig(),
|
||||
available_memory_gb=76,
|
||||
@@ -316,8 +456,12 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
|
||||
self.assertEqual(args.ltx2_two_stage_device_mode, "snapshot")
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.image_encoder_cpu_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components, ["text_encoder", "image_encoder"]
|
||||
)
|
||||
|
||||
def test_auto_wan_layerwise_offload_is_enabled_without_fsdp(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -325,8 +469,14 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
kwargs={"performance_mode": "auto"},
|
||||
)
|
||||
|
||||
self.assertTrue(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components,
|
||||
["default", "text_encoder", "image_encoder"],
|
||||
)
|
||||
|
||||
def test_memory_wan_layerwise_offload_is_enabled_without_fsdp(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -334,8 +484,14 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
kwargs={"performance_mode": "memory"},
|
||||
)
|
||||
|
||||
self.assertTrue(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components,
|
||||
["default", "text_encoder", "image_encoder"],
|
||||
)
|
||||
|
||||
def test_auto_wan_layerwise_offload_does_not_disable_explicit_fsdp(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -348,7 +504,7 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
self.assertFalse(args.dit_layerwise_offload)
|
||||
self.assertFalse(args.layerwise_offload_components)
|
||||
self.assertTrue(args.use_fsdp_inference)
|
||||
|
||||
def test_auto_multi_gpu_wan_uses_layerwise_offload_without_cfg(self):
|
||||
@@ -365,9 +521,52 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.enable_cfg_parallel)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertTrue(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components,
|
||||
["default", "text_encoder", "image_encoder"],
|
||||
)
|
||||
|
||||
def test_auto_multi_gpu_qwen_keeps_legacy_offload_with_cfg(self):
|
||||
def test_explicit_multi_gpu_dit_layerwise_only_selects_default_component(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
MOVAPipelineConfig(),
|
||||
kwargs={
|
||||
"model_path": "OpenMOSS-Team/MOVA-360p",
|
||||
"num_gpus": 2,
|
||||
"dit_layerwise_offload": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["default"])
|
||||
|
||||
def test_auto_multi_gpu_ltx_replaces_component_cpu_offload_with_resident_dit(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
LTX2PipelineConfig(),
|
||||
available_memory_gb=76,
|
||||
kwargs={
|
||||
"model_path": "Lightricks/LTX-2",
|
||||
"num_gpus": 2,
|
||||
"pipeline_class_name": "LTX2TwoStagePipeline",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components, ["text_encoder", "image_encoder"]
|
||||
)
|
||||
|
||||
def test_auto_multi_gpu_qwen_replaces_text_encoder_offload_with_cfg(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
QwenImagePipelineConfig(),
|
||||
kwargs={
|
||||
@@ -380,9 +579,10 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.enable_cfg_parallel)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertFalse(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["text_encoder"])
|
||||
|
||||
def test_auto_multi_gpu_zimage_base_prefers_fsdp(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -424,8 +624,9 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.enable_cfg_parallel)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["text_encoder"])
|
||||
|
||||
def test_auto_multi_gpu_qwen_skips_fsdp_when_available_memory_is_low(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -441,8 +642,9 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.enable_cfg_parallel)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["text_encoder"])
|
||||
|
||||
def test_auto_multi_gpu_qwen_uses_selected_gpu_min_available_memory(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -459,7 +661,7 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.enable_cfg_parallel)
|
||||
|
||||
def test_auto_multi_gpu_qwen_keeps_legacy_offload_with_headroom(self):
|
||||
def test_auto_multi_gpu_qwen_replaces_text_encoder_offload_with_headroom(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
QwenImagePipelineConfig(),
|
||||
available_memory_gb={1: 72, 2: 80},
|
||||
@@ -474,8 +676,9 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.enable_cfg_parallel)
|
||||
self.assertTrue(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(args.layerwise_offload_components, ["text_encoder"])
|
||||
|
||||
def test_speed_mode_single_gpu_disables_offload(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -489,7 +692,7 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
self.assertEqual(args.performance_mode, "speed")
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertFalse(args.dit_layerwise_offload)
|
||||
self.assertFalse(args.layerwise_offload_components)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
|
||||
@@ -518,10 +721,14 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertFalse(args.use_fsdp_inference)
|
||||
self.assertTrue(args.dit_layerwise_offload)
|
||||
self.assertTrue(args.layerwise_offload_components)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
self.assertTrue(args.text_encoder_cpu_offload)
|
||||
self.assertTrue(args.image_encoder_cpu_offload)
|
||||
self.assertFalse(args.text_encoder_cpu_offload)
|
||||
self.assertFalse(args.image_encoder_cpu_offload)
|
||||
self.assertEqual(
|
||||
args.layerwise_offload_components,
|
||||
["default", "text_encoder", "image_encoder"],
|
||||
)
|
||||
|
||||
def test_memory_mode_preserves_explicit_fsdp(self):
|
||||
args = self._from_dict_with_pipeline_config(
|
||||
@@ -535,7 +742,7 @@ class TestOffloadDefaults(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertTrue(args.use_fsdp_inference)
|
||||
self.assertFalse(args.dit_layerwise_offload)
|
||||
self.assertFalse(args.layerwise_offload_components)
|
||||
self.assertFalse(args.dit_cpu_offload)
|
||||
|
||||
def test_invalid_performance_mode_raises(self):
|
||||
|
||||
Reference in New Issue
Block a user