chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,76 @@
from safetensors.torch import load_file as safetensors_load_file
from sglang.multimodal_gen.configs.models.adapter.ltx_2_connector import (
LTX2ConnectorConfig,
)
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.utils import (
_list_safetensors_files,
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.precision import resolve_precision
class AdapterLoader(ComponentLoader):
"""Loader for small adapter-style modules (e.g., LTX-2 connectors).
This loader intentionally avoids FSDP sharding and just:
1) Instantiates the module from `config.json`.
2) Loads a single safetensors state_dict.
"""
component_names = ["connectors"]
expected_library = "diffusers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, *args
):
config = get_diffusers_component_config(component_path=component_model_path)
cls_name = config.pop("_class_name", None)
if cls_name is None:
raise ValueError(
"Model config does not contain a _class_name attribute. "
"Only diffusers format is supported."
)
config.pop("_diffusers_version", None)
config.pop("_name_or_path", None)
server_args.model_paths["connectors"] = component_model_path
model_cls, _ = ModelRegistry.resolve_model_cls(cls_name)
target_device = get_local_torch_device()
default_dtype = resolve_precision(
server_args, "connectors", precision_attr="dit_precision"
)
with set_default_torch_dtype(default_dtype), skip_init_modules():
connector_cfg = LTX2ConnectorConfig()
connector_cfg.update_model_arch(config)
model = model_cls(connector_cfg).to(
device=target_device, dtype=default_dtype
)
safetensors_list = _list_safetensors_files(component_model_path)
if not safetensors_list:
raise ValueError(f"No safetensors files found in {component_model_path}")
if len(safetensors_list) != 1:
raise ValueError(
f"Found {len(safetensors_list)} safetensors files in {component_model_path}, expected 1"
)
loaded = safetensors_load_file(safetensors_list[0])
model.load_state_dict(loaded, strict=False)
return model
@@ -0,0 +1,112 @@
from copy import deepcopy
import torch
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.fsdp_load import maybe_load_fsdp_model
from sglang.multimodal_gen.runtime.loader.utils import _list_safetensors_files
from sglang.multimodal_gen.runtime.loader.weight_load_plan import WeightLoadPlan
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.precision import resolve_precision
logger = init_logger(__name__)
class BridgeLoader(ComponentLoader):
"""Loader for MOVA dual tower bridge with FSDP support."""
pipeline_bridge_config_attr: str = "bridge_config"
component_names = ["dual_tower_bridge"]
expected_library = "diffusers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
config = get_diffusers_component_config(component_path=component_model_path)
hf_config = deepcopy(config)
class_name = config.pop("_class_name", None)
if class_name is None:
raise ValueError(
"Model config does not contain a _class_name attribute. "
"Only diffusers format is supported."
)
server_args.model_paths[component_name] = component_model_path
# Try to get bridge config from pipeline config, fallback to creating one
bridge_config = getattr(
server_args.pipeline_config, self.pipeline_bridge_config_attr, None
)
if bridge_config is not None:
bridge_config.update_model_arch(config)
else:
# Create a minimal config from hf_config
from sglang.multimodal_gen.configs.models.bridges.mova_dual_tower import (
MOVADualTowerConfig,
)
bridge_config = MOVADualTowerConfig()
bridge_config.update_model_arch(config)
model_cls, _ = ModelRegistry.resolve_model_cls(class_name)
# Find all safetensors files
safetensors_list = _list_safetensors_files(component_model_path)
if not safetensors_list:
raise ValueError(f"No safetensors files found in {component_model_path}")
default_dtype = resolve_precision(
server_args, component_name, precision_attr="dit_precision"
)
logger.info(
"Loading %s from %s safetensors files, default_dtype: %s",
class_name,
len(safetensors_list),
default_dtype,
)
# Use the FSDP loader when FSDP is requested or shard rules are declared.
fsdp_shard_conditions = getattr(model_cls, "_fsdp_shard_conditions", None)
if server_args.use_fsdp_inference or (
server_args.hsdp_shard_dim is not None and fsdp_shard_conditions
):
local_torch_device = get_local_torch_device()
# Load with FSDP support
model = maybe_load_fsdp_model(
model_cls=model_cls,
init_params={"config": bridge_config, "hf_config": hf_config},
weight_dir_list=safetensors_list,
device=local_torch_device,
hsdp_replicate_dim=server_args.hsdp_replicate_dim,
hsdp_shard_dim=server_args.hsdp_shard_dim,
cpu_offload=server_args.dit_cpu_offload,
pin_cpu_memory=server_args.pin_cpu_memory,
fsdp_inference=server_args.use_fsdp_inference,
param_dtype=default_dtype,
reduce_dtype=torch.float32,
output_dtype=None,
strict=False,
weight_load_plan=WeightLoadPlan(
checkpoint_load_device=local_torch_device
),
)
else:
# Fallback to simple loading (for non-FSDP or legacy models)
model = model_cls.from_pretrained(
component_model_path, torch_dtype=default_dtype
)
model = model.to(device=get_local_torch_device(), dtype=default_dtype)
total_params = sum(p.numel() for p in model.parameters())
logger.info("Loaded bridge model with %.2fM parameters", total_params / 1e6)
return model
@@ -0,0 +1,587 @@
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
# SPDX-License-Identifier: Apache-2.0
import importlib
import os
import pkgutil
import traceback
from abc import ABC
from typing import Any, Type
import torch
from diffusers import AutoModel
from torch import nn
from transformers import AutoImageProcessor, AutoProcessor, AutoTokenizer
from sglang.multimodal_gen.configs.models import ModelConfig
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.layers.attention.selector import (
component_attn_backend_context_manager,
get_component_attn_backend_context,
)
from sglang.multimodal_gen.runtime.loader.utils import (
_normalize_component_type,
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_DIT_GROUP,
layerwise_component_matches_any_selection,
normalize_layerwise_offload_components,
)
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 (
get_hf_config,
prepare_diffusers_component_path_for_loading,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.precision import resolve_component_precision
logger = init_logger(__name__)
def _load_auto_tokenizer_with_roberta_processing_compat(*args, **kwargs):
from tokenizers import processors
roberta_processing = processors.RobertaProcessing
def roberta_processing_compat(*processor_args, **processor_kwargs):
if "sep" in processor_kwargs and "cls" in processor_kwargs:
sep = processor_kwargs.pop("sep")
cls_token = processor_kwargs.pop("cls")
return roberta_processing(
sep, cls_token, *processor_args, **processor_kwargs
)
return roberta_processing(*processor_args, **processor_kwargs)
processors.RobertaProcessing = roberta_processing_compat
try:
return AutoTokenizer.from_pretrained(*args, **kwargs)
finally:
processors.RobertaProcessing = roberta_processing
class ComponentLoader(ABC):
"""Base class for loading a specific type of model component."""
# the list of possible name of the component in model_index.json, e.g., scheduler
component_names: list[str] = []
# diffusers or transformers
expected_library: str = ""
_loaders_registered = False
def __init_subclass__(cls, **kwargs):
"""
register loaders, called when subclass is imported
"""
super().__init_subclass__(**kwargs)
for component_name in cls.component_names:
component_name_to_loader_cls[component_name] = cls
def __init__(self, device=None) -> None:
self.device = device
self.component_architecture: str | None = None
def should_offload(
self, server_args: ServerArgs, model_config: ModelConfig | None = None
):
# not offload by default
return False
def target_device(self, should_offload):
if should_offload:
return (
torch.device("mps")
if current_platform.is_mps()
else torch.device("cpu")
)
else:
return get_local_torch_device()
def customized_load_kwargs_for_component(
self, _server_args: ServerArgs, _component_name: str
) -> dict[str, Any]:
return {}
def should_raise_customized_load_error(
self, _server_args: ServerArgs, _component_name: str
) -> bool:
return False
@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 = normalize_layerwise_offload_components(
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_DIT_GROUP
}
return layerwise_component_matches_any_selection(
component_name, 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_customized_with_context(
self,
component_model_path: str,
server_args: ServerArgs,
component_name: str,
attn_backend: Any,
component_attn_name: str | None,
) -> AutoModel:
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, **load_kwargs
)
return self._maybe_configure_layerwise_after_startup_cpu_staging(
component, server_args, component_name, load_kwargs
)
def _load_native_with_context(
self,
component_model_path: str,
server_args: ServerArgs,
component_name: str,
transformers_or_diffusers: str,
attn_backend: Any,
component_attn_name: str | None,
) -> AutoModel:
with component_attn_backend_context_manager(
attn_backend, component_name=component_attn_name
):
component = self.load_native(
component_model_path,
server_args,
transformers_or_diffusers,
component_name,
)
should_offload = self.should_offload(server_args)
target_device = self.target_device(should_offload)
return component.to(device=target_device)
def load(
self,
component_model_path: str,
server_args: ServerArgs,
component_name: str,
transformers_or_diffusers: str,
) -> tuple[AutoModel, float]:
"""
Template method that standardizes logging around the core load implementation.
The priority of loading method is:
1. load customized component
2. load native diffusers/transformers component
If all of the above methods failed, an error will be thrown
"""
gpu_mem_before_loading = current_platform.get_available_gpu_memory()
logger.info(
"Loading %s from %s. avail mem: %.2f GB",
component_name,
component_model_path,
gpu_mem_before_loading,
)
attn_backend = None
component_attn_name = None
if get_component_attn_backend_context() is None:
attn_backend, matched_backend_key = (
server_args.resolve_component_attention_backend(component_name)
)
component_attn_name = matched_backend_key or component_name
if attn_backend is not None:
logger.info(
"Using %s backend for component: %s",
attn_backend.name.lower(),
matched_backend_key,
)
try:
component = self._load_customized_with_context(
component_model_path,
server_args,
component_name,
attn_backend,
component_attn_name,
)
source = "sgl-diffusion"
except Exception as e:
if self.should_raise_customized_load_error(server_args, component_name):
traceback.print_exc()
raise RuntimeError(
f"Failed to load customized {component_name}; native fallback "
"is disabled for this component configuration."
) from e
if "Unsupported model architecture" in str(e):
logger.info(
f"Component: {component_name} doesn't have a customized version yet, using native version"
)
else:
traceback.print_exc()
logger.error(
f"Error while loading customized {component_name}, falling back to native version"
)
# fallback to native version
component = self._load_native_with_context(
component_model_path,
server_args,
component_name,
transformers_or_diffusers,
attn_backend,
component_attn_name,
)
source = "native"
logger.warning(
"Native component %s: %s is loaded, performance may be sub-optimal",
component_name,
component.__class__.__name__,
)
if component is None:
logger.error("Load %s failed", component_name)
consumed = 0.0
else:
if isinstance(component, nn.Module):
component = component.eval()
current_gpu_mem = current_platform.get_available_gpu_memory()
model_size = get_memory_usage_of_component(component) or "NA"
consumed = gpu_mem_before_loading - current_gpu_mem
logger.info(
f"Loaded %s: %s ({source} version). model size: %s GB, consumed GPU mem: %.2f GB, avail GPU mem: %.2f GB",
component_name,
component.__class__.__name__,
model_size,
consumed,
current_gpu_mem,
)
return component, consumed
def load_native(
self,
component_model_path: str,
server_args: ServerArgs,
transformers_or_diffusers: str,
component_name: str | None = None,
) -> AutoModel:
"""
Load the component using the native library (transformers/diffusers).
"""
precision = (
resolve_component_precision(server_args, component_name)
if component_name is not None
else None
)
load_kwargs = {}
if precision is not None:
load_kwargs["torch_dtype"] = precision
if transformers_or_diffusers == "transformers":
from transformers import AutoModel
config = get_hf_config(
component_model_path,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
)
return AutoModel.from_pretrained(
component_model_path,
config=config,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
**load_kwargs,
)
elif transformers_or_diffusers == "diffusers":
from diffusers import AutoModel
component_model_path = prepare_diffusers_component_path_for_loading(
component_model_path
)
return AutoModel.from_pretrained(
component_model_path,
revision=server_args.revision,
trust_remote_code=server_args.trust_remote_code,
**load_kwargs,
)
else:
raise ValueError(f"Unsupported library: {transformers_or_diffusers}")
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
"""
Load the customized version component, implemented and optimized in SGL-diffusion
"""
raise NotImplementedError(
f"load_customized not implemented for {self.__class__.__name__}"
)
@classmethod
def _ensure_loaders_registered(cls):
"""
avoid multiple registration
"""
if cls._loaders_registered:
return
package_dir = os.path.dirname(__file__)
package_name = (
__package__
or "sglang.multimodal_gen.runtime.loader.component_loaders.component_loaders"
)
for _, name, _ in pkgutil.iter_modules([package_dir]):
# skip importing self to avoid circular dependency issues
if name == "component_loader":
continue
try:
importlib.import_module(f".{name}", package=package_name)
except ImportError as e:
logger.warning(f"Failed to import loader component {name}: {e}")
cls._loaders_registered = True
@classmethod
def resolve_transformers_or_diffusers(
self, transformers_or_diffusers: str, component_name: str
) -> str:
# NOTE(FlamingoPg): special for LTX-2 models
if component_name == "vocoder" or component_name == "connectors":
transformers_or_diffusers = "diffusers"
# NOTE(CloudRipple): special for MOVA models
# TODO(CloudRipple): remove most of these special cases after unifying the loading logic
if component_name in [
"audio_vae",
"audio_dit",
"dual_tower_bridge",
"video_dit",
]:
transformers_or_diffusers = "diffusers"
if (
component_name == "scheduler"
and transformers_or_diffusers == "mova.diffusion.schedulers.flow_match_pair"
):
transformers_or_diffusers = "diffusers"
return transformers_or_diffusers
@classmethod
def for_component_type(
cls,
component_name: str,
transformers_or_diffusers: str,
component_architecture: str | None = None,
) -> "ComponentLoader":
"""
Factory method to create a component loader for a specific component type.
Args:
component_name: Type of component (e.g., "vae", "text_encoder", "transformer", "scheduler")
transformers_or_diffusers: Whether the component is from transformers or diffusers
"""
cls._ensure_loaders_registered()
# Map of component types to their loader classes and expected library
component_name = _normalize_component_type(component_name)
transformers_or_diffusers = cls.resolve_transformers_or_diffusers(
transformers_or_diffusers, component_name
)
if component_name in component_name_to_loader_cls:
loader_cls: Type[ComponentLoader] = component_name_to_loader_cls[
component_name
]
expected_library = loader_cls.expected_library
# Assert that the library matches what's expected for this component type
assert (
transformers_or_diffusers == expected_library
), f"{component_name} must be loaded from {expected_library}, got {transformers_or_diffusers}"
loader = loader_cls()
loader.component_architecture = component_architecture
return loader
# For unknown component types, use a generic loader
logger.warning(
"No specific loader found for component type: %s. Using generic loader.",
component_name,
)
return GenericComponentLoader(transformers_or_diffusers, component_architecture)
class ImageProcessorLoader(ComponentLoader):
"""Loader for image processor."""
component_names = ["image_processor"]
expected_library = "transformers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
) -> Any:
return AutoImageProcessor.from_pretrained(component_model_path, use_fast=True)
class AutoProcessorLoader(ComponentLoader):
"""Loader for auto processor."""
component_names = ["processor"]
expected_library = "transformers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
) -> Any:
return AutoProcessor.from_pretrained(component_model_path)
class TokenizerLoader(ComponentLoader):
"""Loader for tokenizers."""
component_names = ["tokenizer", "text_tokenizer"]
expected_library = "transformers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
) -> Any:
# Some pipelines keep the slot name `tokenizer` in model_index.json even
# when the declared class is a processor. e.g. FLUX.2:
# `tokenizer: ["transformers", "PixtralProcessor"]`.
# Honor the declared component class instead of guessing from the slot name.
if (
self.component_architecture is not None
and self.component_architecture.endswith("Processor")
):
return AutoProcessor.from_pretrained(component_model_path)
# Qwen-Image's model_index declares Qwen2Tokenizer; using the fast class
# changes text preprocessing and shifts official GT comparisons.
use_fast = self.component_architecture != "Qwen2Tokenizer"
try:
return AutoTokenizer.from_pretrained(
component_model_path,
padding_side="right",
use_fast=use_fast,
)
except TypeError as e:
# tokenizers>=0.21 removed the `cls` kwarg from RobertaProcessing,
# but some transformers CLIPTokenizer builds still pass it. Fall back
# to the pure-Python (slow) tokenizer which avoids the rust path.
if "RobertaProcessing" in str(e) and use_fast:
logger.warning(
"Fast tokenizer failed (%s), retrying with use_fast=False", e
)
return _load_auto_tokenizer_with_roberta_processing_compat(
component_model_path,
padding_side="right",
use_fast=False,
)
raise
class GenericComponentLoader(ComponentLoader):
"""Generic loader for components that don't have a specific loader."""
def __init__(
self, library="transformers", component_architecture: str | None = None
) -> None:
super().__init__()
self.library = library
self.component_architecture = component_architecture
class PipelineComponentLoader:
"""
Utility class for loading the components in a pipeline.
"""
@staticmethod
def load_component(
component_name: str,
component_model_path: str,
transformers_or_diffusers: str,
server_args: ServerArgs,
component_architecture: str | None = None,
):
"""
Load a pipeline component.
Args:
component_name: Name of the component (e.g., "vae", "text_encoder", "transformer", "scheduler")
component_model_path: Path to the component model
transformers_or_diffusers: Whether the component is from transformers or diffusers
component_architecture: the class name of the module
"""
# Get the appropriate loader for this component type
loader = ComponentLoader.for_component_type(
component_name, transformers_or_diffusers, component_architecture
)
try:
# Load the component
return loader.load(
component_model_path,
server_args,
component_name,
transformers_or_diffusers,
)
except Exception as e:
logger.error(
f"Error while loading component: {component_name}, {component_model_path=}"
)
raise e
@@ -0,0 +1,69 @@
from sglang.multimodal_gen.configs.models import ModelConfig
from sglang.multimodal_gen.runtime.loader.component_loaders.text_encoder_loader import (
TextEncoderLoader,
)
from sglang.multimodal_gen.runtime.models.encoders.base import finalize_encoder_folding
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
class ImageEncoderLoader(TextEncoderLoader):
component_names = ["image_encoder"]
expected_library = "transformers"
def should_offload(self, server_args, model_config: ModelConfig | None = None):
should_offload = server_args.image_encoder_cpu_offload
if not should_offload:
return False
# _fsdp_shard_conditions is in arch_config, not directly on model_config
arch_config = (
getattr(model_config, "arch_config", model_config) if model_config else None
)
fsdp_shard_conditions = (
getattr(arch_config, "_fsdp_shard_conditions", []) if arch_config else []
)
use_cpu_offload = should_offload and len(fsdp_shard_conditions) > 0
return use_cpu_offload
def load_customized(
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(
# model=model_path,
# trust_remote_code=server_args.trust_remote_code,
# revision=server_args.revision,
# model_override_args=None,
# )
model_config = get_diffusers_component_config(
component_path=component_model_path
)
encoder_config = server_args.pipeline_config.image_encoder_config
encoder_config.update_model_arch(model_config)
# Keep the proposed fold group only if the encoder is wide enough
# (image encoders are small, so this normally reverts to replicated).
finalize_encoder_folding(encoder_config)
# Always start with local device; load_model will adjust for offload if needed
# TODO(will): add support for other dtypes
return self.load_model(
component_model_path,
encoder_config,
server_args,
server_args.pipeline_config.image_encoder_precision,
cpu_offload_flag=(
cpu_offload_flag
if cpu_offload_flag is not None
else server_args.image_encoder_cpu_offload
),
)
@@ -0,0 +1,162 @@
# SPDX-License-Identifier: Apache-2.0
import json
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
def _read_model_max_length(model_path: str) -> int | None:
"""Read model_max_length from tokenizer_config.json in the given directory."""
config_path = os.path.join(model_path, "tokenizer_config.json")
if os.path.exists(config_path):
try:
with open(config_path, encoding="utf-8") as f:
config = json.load(f)
val = config.get("model_max_length")
if val is not None:
return int(val)
except Exception as e:
logger.warning(
"Failed to read tokenizer_config.json from %s: %s", model_path, e
)
return None
class PEModelWrapper:
def __init__(self, model, tokenizer, device, model_max_length: int):
self.model = model
self.pe_tokenizer = tokenizer
self.device = device
self.model_max_length = model_max_length
def generate(self, prompt: str, sampling_params: dict) -> dict:
inputs = self.pe_tokenizer(
prompt,
return_tensors="pt",
truncation=True,
max_length=self.model_max_length,
).to(self.device)
input_len = inputs["input_ids"].shape[1]
generate_kwargs = dict(
**inputs,
max_new_tokens=sampling_params.get("max_new_tokens", self.model_max_length),
do_sample=True,
)
temperature = sampling_params.get("temperature")
top_p = sampling_params.get("top_p")
if temperature is not None:
generate_kwargs["temperature"] = temperature
if top_p is not None:
generate_kwargs["top_p"] = top_p
with torch.no_grad():
output_ids = self.model.generate(**generate_kwargs)
new_tokens = output_ids[0, input_len:]
text = self.pe_tokenizer.decode(new_tokens, skip_special_tokens=True)
return {"text": text}
def to(self, *args, **kwargs):
"""Move underlying model to device."""
self.model = self.model.to(*args, **kwargs)
if args:
device = args[0]
if isinstance(device, (str, torch.device)):
self.device = torch.device(device)
return self
class PELoader(ComponentLoader):
"""Loader for prompt-enhancement causal LM (Ministral-3 based)."""
component_names = ["pe"]
expected_library = "transformers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
logger.info("Loading PE model from %s ...", component_model_path)
pe_tokenizer_dir = os.path.join(
os.path.dirname(component_model_path), "pe_tokenizer"
)
if not os.path.exists(
os.path.join(component_model_path, "tokenizer_config.json")
) and os.path.exists(os.path.join(pe_tokenizer_dir, "tokenizer_config.json")):
tokenizer_path = pe_tokenizer_dir
logger.info(
"PE tokenizer files not found in %s, using %s",
component_model_path,
tokenizer_path,
)
else:
tokenizer_path = component_model_path
model_max_length = _read_model_max_length(tokenizer_path)
if model_max_length is None:
raise RuntimeError(
f"Cannot load PE model: 'model_max_length' not found in "
f"{os.path.join(tokenizer_path, 'tokenizer_config.json')}. "
"Please ensure the PE component directory (or its sibling "
"pe_tokenizer/ directory) contains a valid tokenizer_config.json "
"with a 'model_max_length' field."
)
logger.info(
"PE model_max_length=%d (from tokenizer_config.json)", model_max_length
)
tokenizer = AutoTokenizer.from_pretrained(
tokenizer_path,
trust_remote_code=server_args.trust_remote_code,
)
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id
attn_impl = "flash_attention_2"
try:
model = AutoModelForCausalLM.from_pretrained(
component_model_path,
torch_dtype=torch.bfloat16,
trust_remote_code=server_args.trust_remote_code,
attn_implementation=attn_impl,
)
logger.info("PE model: using Flash Attention 2")
except (ValueError, ImportError):
logger.warning("Flash Attention 2 not available, falling back to SDPA")
attn_impl = "sdpa"
model = AutoModelForCausalLM.from_pretrained(
component_model_path,
torch_dtype=torch.bfloat16,
trust_remote_code=server_args.trust_remote_code,
attn_implementation=attn_impl,
)
device = get_local_torch_device()
model = model.to(device).eval()
logger.info(
"PE model loaded on %s: %s (attn=%s)",
device,
model.__class__.__name__,
attn_impl,
)
return PEModelWrapper(
model=model,
tokenizer=tokenizer,
device=device,
model_max_length=model_max_length,
)
@@ -0,0 +1,48 @@
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
class SchedulerLoader(ComponentLoader):
"""Loader for scheduler."""
component_names = ["scheduler"]
expected_library = "diffusers"
def load_customized(
self, component_model_path: str, server_args: ServerArgs, *args
):
"""Load the scheduler based on the model path, and inference args."""
config = get_diffusers_component_config(component_path=component_model_path)
checkpoint_class_name = config.pop("_class_name", None)
class_name = (
getattr(server_args.pipeline_config, "scheduler_class_override", None)
or checkpoint_class_name
)
assert (
class_name is not None
), "Model config does not contain a _class_name attribute. Only diffusers format is supported."
if checkpoint_class_name is not None and class_name != checkpoint_class_name:
logger.info(
"Overriding scheduler class from %s to %s",
checkpoint_class_name,
class_name,
)
scheduler_cls, _ = ModelRegistry.resolve_model_cls(class_name)
scheduler = scheduler_cls(**config)
if server_args.pipeline_config.flow_shift is not None:
scheduler.set_shift(server_args.pipeline_config.flow_shift)
return scheduler
@@ -0,0 +1,76 @@
# SPDX-License-Identifier: Apache-2.0
from safetensors.torch import load_file as safetensors_load_file
from sglang.multimodal_gen.configs.models import ModelConfig
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.utils import (
_list_safetensors_files,
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
logger = init_logger(__name__)
class SoundTokenizerLoader(ComponentLoader):
component_names = ["sound_tokenizer"]
expected_library = "diffusers"
def should_offload(
self, server_args: ServerArgs, model_config: ModelConfig | None = None
) -> bool:
return server_args.vae_cpu_offload
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None) or self.component_architecture
assert (
class_name is not None
), "Sound tokenizer class name must be available from component config."
server_args.model_paths[component_name] = component_model_path
try:
precision = server_args.pipeline_config.vae_precision
except AttributeError:
precision = "bf16"
dtype = PRECISION_TO_TYPE[precision]
target_device = self.target_device(self.should_offload(server_args))
with set_default_torch_dtype(dtype), skip_init_modules():
model_cls, _ = ModelRegistry.resolve_model_cls(class_name)
model = model_cls(config).to(target_device)
safetensors_list = _list_safetensors_files(component_model_path)
assert (
len(safetensors_list) == 1
), f"Found {len(safetensors_list)} safetensors files in {component_model_path}"
loaded = safetensors_load_file(safetensors_list[0])
incompatible = model.load_state_dict(loaded, strict=False)
missing = getattr(incompatible, "missing_keys", [])
# The tokenizer is decoder-only; the checkpoint's encoder weights are
# expected leftovers, so they're excluded from the load warning.
unexpected = [
k
for k in getattr(incompatible, "unexpected_keys", [])
if not k.startswith("encoder.")
]
if missing or unexpected:
logger.warning(
"Loaded sound_tokenizer with missing_keys=%d unexpected_keys=%d",
len(missing),
len(unexpected),
)
model.eval()
return model
@@ -0,0 +1,489 @@
import dataclasses
import glob
import os
import re
from collections.abc import Generator, Iterable
from contextlib import nullcontext
from typing import cast
import torch
import torch.distributed as dist
from torch import nn
from torch.distributed import init_device_mesh
from transformers.utils import SAFE_WEIGHTS_INDEX_NAME
from sglang.multimodal_gen.configs.models import EncoderConfig, ModelConfig
from sglang.multimodal_gen.configs.pipeline_configs.qwen_image import (
QwenImageEditPipelineConfig,
)
from sglang.multimodal_gen.runtime.distributed import (
get_local_torch_device,
get_tp_group,
)
from sglang.multimodal_gen.runtime.distributed.group_coordinator import GroupCoordinator
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
patch_tensor_parallel_group,
)
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.fsdp_load import shard_model
from sglang.multimodal_gen.runtime.loader.utils import (
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.loader.weight_utils import (
filter_duplicate_safetensors_files,
filter_files_not_needed_for_inference,
pt_weights_iterator,
safetensors_weights_iterator,
)
from sglang.multimodal_gen.runtime.models.encoders.base import (
finalize_encoder_folding,
get_folding_tp_group,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
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 (
get_config,
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.precision import precision_to_dtype
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
from sglang.srt.environ import envs
logger = init_logger(__name__)
class TextEncoderLoader(ComponentLoader):
"""Loader for text encoders."""
component_names = ["text_encoder"]
expected_library = "transformers"
@dataclasses.dataclass
class Source:
"""A source for weights."""
model_or_path: str
"""The model ID or path."""
prefix: str = ""
"""A prefix to prepend to all weights."""
fall_back_to_pt: bool = True
"""Whether .pt weights can be used."""
allow_patterns_overrides: list[str] | None = None
"""If defined, weights will load exclusively using these patterns."""
def should_offload(self, server_args, model_config: ModelConfig | None = None):
should_offload = server_args.text_encoder_cpu_offload
if not should_offload:
return False
# _fsdp_shard_conditions is in arch_config, not directly on model_config
arch_config = (
getattr(model_config, "arch_config", model_config) if model_config else None
)
fsdp_shard_conditions = (
getattr(arch_config, "_fsdp_shard_conditions", []) if arch_config else []
)
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,
server_args: ServerArgs,
transformers_or_diffusers: str,
component_name: str | None = None,
):
if transformers_or_diffusers != "transformers":
return super().load_native(
component_model_path,
server_args,
transformers_or_diffusers,
component_name,
)
encoder_idx = (
self._extract_encoder_index(component_name or "text_encoder_2")
if component_name
else 1 if component_model_path.rstrip("/").endswith("text_encoder_2") else 0
)
encoder_dtype = server_args.pipeline_config.text_encoder_precisions[encoder_idx]
dtype = precision_to_dtype(
encoder_dtype,
f"text_encoder_precisions[{encoder_idx}]",
)
transformers_model_class = self._resolve_transformers_text_encoder_class(
component_model_path, server_args
)
return transformers_model_class.from_pretrained(
component_model_path,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
torch_dtype=dtype,
)
@staticmethod
def _resolve_transformers_text_encoder_class(component_model_path, server_args):
"""Resolve the concrete transformers class for a text encoder.
AutoModel maps encoder-decoder model types (e.g. T5/UMT5) to full
seq2seq classes, whose forward expects decoder inputs and raises when
the module is used purely as a text encoder. For such checkpoints,
prefer the encoder-only class from the config architectures or map the
full seq2seq architecture to its encoder-only counterpart. Encoders that
are not encoder-decoder keep using AutoModel unchanged.
"""
import transformers
from transformers import AutoConfig, AutoModel
try:
config = AutoConfig.from_pretrained(
component_model_path,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
)
except Exception:
return AutoModel
if getattr(config, "is_encoder_decoder", False):
encoder_only_map = {
"T5Model": "T5EncoderModel",
"T5ForConditionalGeneration": "T5EncoderModel",
"UMT5Model": "UMT5EncoderModel",
"UMT5ForConditionalGeneration": "UMT5EncoderModel",
"MT5Model": "MT5EncoderModel",
"MT5ForConditionalGeneration": "MT5EncoderModel",
}
for arch in getattr(config, "architectures", None) or []:
encoder_arch = encoder_only_map.get(arch, arch)
transformers_model_class = getattr(transformers, encoder_arch, None)
if isinstance(transformers_model_class, type):
return transformers_model_class
return AutoModel
def _prepare_weights(
self,
model_name_or_path: str,
fall_back_to_pt: bool,
allow_patterns_overrides: list[str] | None,
) -> tuple[str, list[str], bool]:
"""Prepare weights for the model.
If the model is not local, it will be downloaded."""
# model_name_or_path = (self._maybe_download_from_modelscope(
# model_name_or_path, revision) or model_name_or_path)
is_local = os.path.isdir(model_name_or_path)
assert is_local, "Model path must be a local directory"
use_safetensors = False
index_file = SAFE_WEIGHTS_INDEX_NAME
allow_patterns = ["*.safetensors", "*.bin"]
if fall_back_to_pt:
allow_patterns += ["*.pt"]
if allow_patterns_overrides is not None:
allow_patterns = allow_patterns_overrides
hf_folder = model_name_or_path
hf_weights_files: list[str] = []
for pattern in allow_patterns:
hf_weights_files += glob.glob(os.path.join(hf_folder, pattern))
if len(hf_weights_files) > 0:
if pattern == "*.safetensors":
use_safetensors = True
break
if use_safetensors:
hf_weights_files = filter_duplicate_safetensors_files(
hf_weights_files, hf_folder, index_file
)
else:
hf_weights_files = filter_files_not_needed_for_inference(hf_weights_files)
if len(hf_weights_files) == 0:
raise RuntimeError(
f"Cannot find any model weights with `{model_name_or_path}`"
)
# Sort weight files when SGLANG_SORT_WEIGHT_FILES >= 0 (default).
# Staggering is not applicable to text-encoder loading (no TP split).
if envs.SGLANG_SORT_WEIGHT_FILES.get() >= 0:
hf_weights_files.sort()
return hf_folder, hf_weights_files, use_safetensors
def _get_weights_iterator(
self,
source: "Source",
to_cpu: bool,
) -> Generator[tuple[str, torch.Tensor], None, None]:
"""get an iterator for the model weights based on the load format."""
hf_folder, hf_weights_files, use_safetensors = self._prepare_weights(
source.model_or_path,
source.fall_back_to_pt,
source.allow_patterns_overrides,
)
if use_safetensors:
weights_iterator = safetensors_weights_iterator(
hf_weights_files,
to_cpu=to_cpu,
)
else:
weights_iterator = pt_weights_iterator(hf_weights_files, to_cpu=to_cpu)
# apply the prefix.
return ((source.prefix + name, tensor) for (name, tensor) in weights_iterator)
def _get_all_weights(
self,
model: nn.Module,
model_path: str,
to_cpu: bool,
) -> Generator[tuple[str, torch.Tensor], None, None]:
primary_weights = TextEncoderLoader.Source(
model_path,
prefix="",
fall_back_to_pt=getattr(model, "fall_back_to_pt_during_load", True),
allow_patterns_overrides=getattr(model, "allow_patterns_overrides", None),
)
yield from self._get_weights_iterator(
primary_weights,
to_cpu,
)
secondary_weights = cast(
Iterable[TextEncoderLoader.Source],
getattr(model, "secondary_weights", ()),
)
for source in secondary_weights:
yield from self._get_weights_iterator(
source,
to_cpu,
)
def load_customized(
self,
component_model_path: str,
server_args: ServerArgs,
component_name: str,
cpu_offload_flag: bool | None = None,
):
"""Load the text encoders based on the model path, and inference args."""
diffusers_pretrained_config = get_config(
component_model_path, trust_remote_code=True
)
model_config = get_diffusers_component_config(
component_path=component_model_path
)
# TODO(mick): had to throw an exception for different text-encoder arch
encoder_index = self._extract_encoder_index(component_name)
assert encoder_index < len(
server_args.pipeline_config.text_encoder_configs
) and encoder_index < len(server_args.pipeline_config.text_encoder_precisions)
encoder_config = server_args.pipeline_config.text_encoder_configs[encoder_index]
encoder_config.update_model_arch(model_config)
if encoder_index == 0:
for key, value in diffusers_pretrained_config.__dict__.items():
setattr(encoder_config.arch_config, key, value)
post_diffusers_config_update = getattr(
encoder_config, "post_diffusers_config_update", None
)
if post_diffusers_config_update is not None:
post_diffusers_config_update()
# Real dims are populated now; keep the proposed fold group only if this
# encoder is actually wide enough to benefit at its real size.
finalize_encoder_folding(encoder_config)
encoder_dtype = server_args.pipeline_config.text_encoder_precisions[
encoder_index
]
# TODO(will): add support for other dtypes
return self.load_model(
component_model_path,
encoder_config,
server_args,
encoder_dtype,
cpu_offload_flag=cpu_offload_flag,
)
@staticmethod
def _extract_encoder_index(component_name: str) -> int:
"""
Map text encoder component names to zero-based indices.
Examples:
- text_encoder -> 0
- text_encoder_2 -> 1
- text_encoder_3 -> 2
"""
match = re.search(r"_(\d+)$", component_name)
if match is None:
return 0
suffix_num = int(match.group(1))
if suffix_num <= 0:
raise ValueError(
f"Invalid text encoder component name '{component_name}': "
"numeric suffix must be >= 1."
)
return suffix_num - 1
def load_model(
self,
model_path: str,
model_config: EncoderConfig,
server_args: ServerArgs,
dtype: str = "fp16",
cpu_offload_flag: bool | None = None,
):
# Determine CPU offload behavior and target device
local_torch_device = get_local_torch_device()
if not current_platform.is_cpu():
fsdp_cpu_offload = self.should_offload(server_args, model_config)
should_offload = (
cpu_offload_flag if cpu_offload_flag is not None else fsdp_cpu_offload
)
else:
fsdp_cpu_offload = False
should_offload = False
if (
getattr(
model_config.arch_config, "requires_gpu_resident_text_encoder", False
)
and should_offload
):
logger.warning(
"Keeping bitsandbytes 4-bit text encoder GPU-resident; CUDA "
"weights and quant states are required for this checkpoint."
)
should_offload = False
if should_offload and not current_platform.is_mps():
model_device = torch.device("cpu")
else:
model_device = local_torch_device
# Parallel folding: build + shard the encoder over the folding group (the
# idle DiT replica during the encoding stage) instead of the default TP
# group, so every encoder folds without threading the group through each layer.
fold_ctx = nullcontext()
if getattr(model_config, "parallel_folding_mode", None) is not None:
folding_group = get_folding_tp_group(model_config)
if (
isinstance(folding_group, GroupCoordinator)
and folding_group is not get_tp_group()
):
fold_ctx = patch_tensor_parallel_group(folding_group)
# patch tp group with folding group to achieve TP among folding group
with fold_ctx, set_default_torch_dtype(PRECISION_TO_TYPE[dtype]):
with model_device, skip_init_modules():
architectures = getattr(model_config, "architectures", [])
model_cls, _ = ModelRegistry.resolve_model_cls(architectures)
enable_image_understanding = (
True
if isinstance(
server_args.pipeline_config, QwenImageEditPipelineConfig
)
else False
)
model_config.enable_image_understanding = enable_image_understanding
model = model_cls(model_config)
weights_to_load = {name for name, _ in model.named_parameters()}
loaded_weights = model.load_weights(
self._get_all_weights(
model,
model_path,
to_cpu=should_offload,
)
)
if should_offload:
# Disable FSDP for MPS as it's not compatible
if current_platform.is_mps():
logger.info(
"Disabling FSDP sharding for MPS platform as it's not compatible"
)
model = model.to(local_torch_device)
elif fsdp_cpu_offload:
mesh = init_device_mesh(
current_platform.device_type,
mesh_shape=(1, dist.get_world_size()),
mesh_dim_names=("offload", "replicate"),
)
shard_model(
model,
cpu_offload=True,
reshard_after_forward=True,
mesh=mesh["offload"],
fsdp_shard_conditions=model_config.arch_config._fsdp_shard_conditions
or getattr(model, "_fsdp_shard_conditions", None),
pin_cpu_memory=server_args.pin_cpu_memory,
)
else:
model = model.to("cpu")
else:
model = model.to(local_torch_device)
# We only enable strict check for non-quantized models
# that have loaded weights tracking currently.
# if loaded_weights is not None:
weights_not_loaded = weights_to_load - loaded_weights
if weights_not_loaded:
# NOTE:
# If we silently continue with uninitialized weights, the text encoder can
# produce NaNs/garbage embeddings that later fail stage verification in a
# hard-to-debug way (e.g., `prompt_embeds` fails the NaN check).
#
# We allow a small set of known-optional parameters to be missing, but
# default to strict behavior for the rest.
allowed_missing_patterns = (
getattr(model, "_allowed_missing_weights_patterns", []) or []
)
unexpected_missing = {
n
for n in weights_not_loaded
if not any(pat in n for pat in allowed_missing_patterns)
}
if unexpected_missing:
raise ValueError(
"Following text encoder weights were not initialized from checkpoint: "
f"{sorted(unexpected_missing)}. "
"This usually indicates a checkpoint/model-arch mismatch or a broken "
"weight-name mapping. If these are truly optional, set "
"`model._allowed_missing_weights_patterns` to whitelist patterns."
)
logger.warning(
"Following (allowed) text encoder weights were not initialized from "
"checkpoint: %s (allowed patterns: %s)",
sorted(weights_not_loaded),
allowed_missing_patterns,
)
return model
@@ -0,0 +1,199 @@
import copy
import logging
from typing import Any
import torch
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.fsdp_load import maybe_load_fsdp_model
from sglang.multimodal_gen.runtime.loader.transformer_load_utils import (
resolve_transformer_quant_load_spec,
resolve_transformer_safetensors_to_load,
)
from sglang.multimodal_gen.runtime.loader.utils import _normalize_component_type
from sglang.multimodal_gen.runtime.loader.weight_load_plan import WeightLoadPlan
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import get_log_level, init_logger
from sglang.srt.utils import is_npu
_is_npu = is_npu()
logger = init_logger(__name__)
def _server_args_for_transformer_component(
server_args: ServerArgs, component_name: str
) -> ServerArgs:
"""Mask global quantized override flags for secondary transformer components."""
if component_name not in ("transformer_2", "unconditional_transformer"):
return server_args
# Some pipelines have secondary DiT components with their own quantized
# weight file. Keep the mapping model-owned and the loader generic.
component_weights_paths = getattr(
server_args, "component_transformer_weights_paths", {}
)
component_weights_path = component_weights_paths.get(component_name)
if component_weights_path is not None:
component_server_args = copy.copy(server_args)
component_server_args.transformer_weights_path = component_weights_path
component_server_args.nunchaku_config = None
logger.info(
"Using transformer_weights_path override for %s: %s",
component_name,
component_weights_path,
)
return component_server_args
if (
server_args.transformer_weights_path is None
and server_args.nunchaku_config is None
):
return server_args
component_server_args = copy.copy(server_args)
component_server_args.transformer_weights_path = None
component_server_args.nunchaku_config = None
logger.info(
"Ignoring global transformer_weights_path for %s; keep it on the base "
"checkpoint unless a per-component override path is provided.",
component_name,
)
return component_server_args
class TransformerLoader(ComponentLoader):
"""Shared loader for (video/audio) DiT transformers."""
component_names = [
"transformer",
"unconditional_transformer",
"audio_dit",
"video_dit",
]
expected_library = "diffusers"
def should_raise_customized_load_error(
self, server_args: ServerArgs, component_name: str
) -> bool:
component_server_args = _server_args_for_transformer_component(
server_args, component_name
)
# Don't let a quantized load quietly fall back to the unquantized native
# model. That would drop the requested precision and bury the real error.
return (
component_server_args.transformer_weights_path is not None
or component_server_args.quantization is not None
)
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
"""Load the transformer based on the model path, and inference args."""
component_server_args = _server_args_for_transformer_component(
server_args, component_name
)
# 1. hf config
config = get_diffusers_component_config(component_path=component_model_path)
safetensors_list = resolve_transformer_safetensors_to_load(
component_server_args, component_model_path
)
# 2. dit config
# Config from Diffusers supersedes sgl_diffusion's model config
component_name = _normalize_component_type(component_name)
server_args.model_paths[component_name] = component_model_path
if component_name in ("transformer", "unconditional_transformer", "video_dit"):
pipeline_dit_config_attr = "dit_config"
elif component_name in ("audio_dit",):
pipeline_dit_config_attr = "audio_dit_config"
else:
raise ValueError(f"Invalid module name: {component_name}")
dit_config = getattr(server_args.pipeline_config, pipeline_dit_config_attr)
dit_config.update_model_arch(config)
cls_name = config.pop("_class_name")
model_cls, _ = ModelRegistry.resolve_model_cls(cls_name)
quant_spec = resolve_transformer_quant_load_spec(
hf_config=config,
server_args=component_server_args,
safetensors_list=safetensors_list,
component_model_path=component_model_path,
model_cls=model_cls,
cls_name=cls_name,
)
logger.info(
"Loading %s from %s safetensors file(s) %s, param_dtype: %s",
cls_name,
len(safetensors_list),
f": {safetensors_list}" if get_log_level() == logging.DEBUG else "",
quant_spec.param_dtype,
)
# prepare init_param
init_params: dict[str, Any] = {
"config": dit_config,
"hf_config": config,
"quant_config": quant_spec.runtime_quant_config,
}
if (
init_params["quant_config"] is None
and component_server_args.transformer_weights_path is not None
):
logger.warning(
"transformer_weights_path provided, but quantization config not resolved, which is unexpected and likely to cause errors"
)
else:
logger.debug("quantization config: %s", init_params["quant_config"])
local_torch_device = get_local_torch_device()
weight_load_plan = WeightLoadPlan.for_component(
checkpoint_load_device=local_torch_device,
needs_device_weight_postprocess=quant_spec.needs_device_weight_postprocess,
component_cpu_offload=bool(component_server_args.dit_cpu_offload),
)
# Load the model using FSDP loader
model = maybe_load_fsdp_model(
model_cls=model_cls,
init_params=init_params,
weight_dir_list=safetensors_list,
device=local_torch_device,
hsdp_replicate_dim=server_args.hsdp_replicate_dim,
hsdp_shard_dim=server_args.hsdp_shard_dim,
cpu_offload=component_server_args.dit_cpu_offload,
pin_cpu_memory=component_server_args.pin_cpu_memory,
fsdp_inference=component_server_args.use_fsdp_inference,
param_dtype=quant_spec.param_dtype,
reduce_dtype=torch.float32,
output_dtype=None,
strict=False,
weight_load_plan=weight_load_plan,
)
# post-hooks (e.g., patch scales (nunchaku))
for post_load_hook in quant_spec.post_load_hooks:
post_load_hook(model)
# considering the existent of mixed-precision models (e.g., nunchaku)
if (
next(model.parameters()).dtype != quant_spec.param_dtype
and quant_spec.param_dtype
):
logger.warning(
"Model dtype does not match expected param dtype, %s vs %s",
next(model.parameters()).dtype,
quant_spec.param_dtype,
)
return model
@@ -0,0 +1,223 @@
import glob
import json
import os
import re
import safetensors
import torch
from safetensors.torch import load_file as safetensors_load_file
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.models.upsampler.latent_upsampler import (
LatentUpsampler,
)
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import maybe_download_model
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
UPSAMPLER_CONSTRUCTOR_KEYS = {
"in_channels",
"mid_channels",
"num_blocks_per_stage",
"dims",
"spatial_upsample",
"temporal_upsample",
"spatial_scale",
"rational_resampler",
}
_HF_BLOB_URL_RE = re.compile(
r"https?://huggingface\.co/([^/]+/[^/]+)/blob/([^/]+)/(.*)"
)
_HF_RESOLVE_URL_RE = re.compile(
r"https?://huggingface\.co/([^/]+/[^/]+)/resolve/([^/]+)/(.*)"
)
def _parse_hf_url(path: str):
m = _HF_BLOB_URL_RE.match(path) or _HF_RESOLVE_URL_RE.match(path)
if m:
return m.group(1), m.group(2), m.group(3)
return None
def _download_hf_file(repo_id: str, filename: str, revision: str = "main") -> str:
from huggingface_hub import hf_hub_download
logger.info("Downloading %s from %s (revision=%s)", filename, repo_id, revision)
return hf_hub_download(repo_id=repo_id, filename=filename, revision=revision)
def _find_safetensors_file(path: str) -> str:
"""Resolve path to a single safetensors file (local path, directory, HF URL, or HF repo id)."""
if os.path.isfile(path) and path.endswith(".safetensors"):
return path
if os.path.isdir(path):
files = sorted(glob.glob(os.path.join(path, "*.safetensors")))
if len(files) == 1:
return files[0]
elif len(files) > 1:
raise ValueError(
f"Found {len(files)} safetensors files in {path}, expected 1"
)
hf = _parse_hf_url(path)
if hf:
repo_id, revision, filename = hf
return _download_hf_file(repo_id, filename, revision)
try:
maybe_downloaded = maybe_download_model(path)
if os.path.isdir(maybe_downloaded):
files = sorted(glob.glob(os.path.join(maybe_downloaded, "*.safetensors")))
if len(files) == 1:
return files[0]
elif len(files) > 1:
raise ValueError(
f"Found {len(files)} safetensors files in {maybe_downloaded}, expected 1"
)
except Exception:
pass
raise FileNotFoundError(
f"No safetensors file found at {path}. "
"Provide a local .safetensors file, a directory containing one, "
"a HuggingFace URL (https://huggingface.co/<repo>/blob/main/<path>), "
"or a HuggingFace repo id."
)
def _normalize_config(raw: dict) -> dict:
"""Map diffusers / original-repo config fields to LatentUpsampler kwargs."""
config = {k: v for k, v in raw.items() if k in UPSAMPLER_CONSTRUCTOR_KEYS}
# diffusers uses rational_spatial_scale instead of rational_resampler + spatial_scale
if "rational_spatial_scale" in raw and "rational_resampler" not in config:
config["rational_resampler"] = True
config.setdefault("spatial_scale", raw["rational_spatial_scale"])
return config
def _infer_config_from_state_dict(state_dict: dict[str, torch.Tensor]) -> dict:
"""Infer LatentUpsampler kwargs from weight shapes and key names.
Works even when no config.json or safetensors metadata is available.
"""
config: dict = {}
w = state_dict.get("initial_conv.weight")
if w is not None:
config["mid_channels"] = w.shape[0]
config["in_channels"] = w.shape[1]
config["dims"] = 3 if w.ndim == 5 else 2
num_blocks = sum(
1
for k in state_dict
if k.startswith("res_blocks.") and k.endswith(".conv1.weight")
)
if num_blocks > 0:
config["num_blocks_per_stage"] = num_blocks
# Detect upsampler type from key patterns
has_rational = any(k.startswith("upsampler.blur_down.") for k in state_dict)
if has_rational:
config["rational_resampler"] = True
config["spatial_upsample"] = True
config["temporal_upsample"] = False
config["spatial_scale"] = 2.0
else:
up_w = state_dict.get("upsampler.0.weight")
if up_w is not None and up_w.ndim == 5:
ratio = up_w.shape[0] // up_w.shape[1]
if ratio == 8:
config["spatial_upsample"] = True
config["temporal_upsample"] = True
elif ratio == 2:
config["spatial_upsample"] = False
config["temporal_upsample"] = True
else:
config["spatial_upsample"] = True
config["temporal_upsample"] = False
else:
config["spatial_upsample"] = True
config["temporal_upsample"] = False
return config
def _load_config(
safetensors_path: str,
original_path: str,
state_dict: dict[str, torch.Tensor],
) -> dict:
"""Load upsampler config with fallback chain:
1. safetensors metadata ("config" key) - original LTX-2 repo format
2. sibling config.json - diffusers format
3. config.json from HF (if original_path was a URL)
4. infer from state dict shapes (always works)
"""
with safetensors.safe_open(safetensors_path, framework="pt") as f:
meta = f.metadata()
if meta and "config" in meta:
logger.info("Using config from safetensors metadata")
return _normalize_config(json.loads(meta["config"]))
config_json_path = os.path.join(os.path.dirname(safetensors_path), "config.json")
if os.path.isfile(config_json_path):
with open(config_json_path) as fp:
logger.info("Using config from sibling config.json")
return _normalize_config(json.load(fp))
hf = _parse_hf_url(original_path)
if hf:
repo_id, revision, filename = hf
config_filename = os.path.dirname(filename) + "/config.json"
try:
local = _download_hf_file(repo_id, config_filename, revision)
with open(local) as fp:
logger.info("Using config from HF config.json")
return _normalize_config(json.load(fp))
except Exception:
pass
logger.info("No explicit config found, inferring from state dict")
return _infer_config_from_state_dict(state_dict)
class UpsamplerLoader(ComponentLoader):
component_names = ["spatial_upsampler"]
expected_library = "diffusers"
def should_offload(self, server_args: ServerArgs, model_config=None):
return server_args.vae_cpu_offload
def load_customized(
self,
component_model_path: str,
server_args: ServerArgs,
component_name: str,
):
safetensors_path = _find_safetensors_file(component_model_path)
state_dict = safetensors_load_file(safetensors_path)
config = _load_config(safetensors_path, component_model_path, state_dict)
logger.info("Loading LatentUpsampler with config: %s", config)
should_offload = self.should_offload(server_args)
target_device = self.target_device(should_offload)
with torch.device("meta"):
model = LatentUpsampler(**config)
model.load_state_dict(state_dict, assign=True)
model = model.to(device=target_device, dtype=torch.bfloat16).eval()
logger.info("Loaded LatentUpsampler to %s", target_device)
return model
@@ -0,0 +1,211 @@
import importlib.util
import os
import torch
import torch.nn as nn
from safetensors.torch import load_file as safetensors_load_file
from sglang.multimodal_gen.configs.models import ModelConfig
from sglang.multimodal_gen.configs.pipeline_configs.ltx_2 import LTX2PipelineConfig
from sglang.multimodal_gen.configs.pipeline_configs.qwen_image import (
QwenImagePipelineConfig,
)
from sglang.multimodal_gen.configs.pipeline_configs.wan import WanT2V480PConfig
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.utils import (
_list_safetensors_files,
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.platforms import current_platform
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.common import get_bool_env_var
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.precision import resolve_component_precision
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
logger = init_logger(__name__)
VAE_CHANNELS_LAST_3D_ENV = "SGLANG_DIFFUSION_VAE_CHANNELS_LAST_3D"
def _backfill_ltx2_audio_vae_latent_stats(
loaded: dict[str, torch.Tensor], component_name: str
) -> None:
if component_name != "audio_vae":
return
mean_key = "per_channel_statistics.mean-of-means"
std_key = "per_channel_statistics.std-of-means"
if "latents_mean" not in loaded and mean_key in loaded:
loaded["latents_mean"] = loaded[mean_key]
if "latents_std" not in loaded and std_key in loaded:
loaded["latents_std"] = loaded[std_key]
def _convert_conv3d_weights_to_channels_last_3d(module: nn.Module) -> int:
"""
Convert Conv3d weights to channels_last_3d (NDHWC) memory format.
Returns the number of Conv3d modules converted.
"""
if not hasattr(torch, "channels_last_3d"):
return 0
num_converted = 0
for m in module.modules():
if isinstance(m, nn.Conv3d):
try:
m.weight.data = m.weight.data.to(memory_format=torch.channels_last_3d)
num_converted += 1
except Exception:
# Best-effort; skip unsupported cases.
continue
return num_converted
def _should_use_channels_last_3d(
server_args: ServerArgs | None, component_name: str
) -> bool:
if component_name not in (
"vae",
"video_vae",
) or not (current_platform.is_cuda() or current_platform.is_rocm()):
return False
override = os.getenv(VAE_CHANNELS_LAST_3D_ENV)
if override is not None and override.strip().lower() != "auto":
return get_bool_env_var(VAE_CHANNELS_LAST_3D_ENV)
if server_args is None:
return False
pipeline_config = server_args.pipeline_config
if isinstance(pipeline_config, QwenImagePipelineConfig):
return True
if (
isinstance(pipeline_config, (WanT2V480PConfig, LTX2PipelineConfig))
and server_args.num_gpus == 1
):
return True
return False
class VAELoader(ComponentLoader):
"""Shared loader for (video/audio) VAE modules."""
component_names = ["vae", "audio_vae", "video_vae"]
expected_library = "diffusers"
def should_offload(
self, server_args: ServerArgs, model_config: ModelConfig | None = None
):
return server_args.vae_cpu_offload
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
"""Load the VAE based on the model path, and inference args."""
config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None)
assert (
class_name is not None
), "Model config does not contain a _class_name attribute. Only diffusers format is supported."
server_args.model_paths[component_name] = component_model_path
if component_name in ("vae", "video_vae"):
pipeline_vae_config_attr = "vae_config"
pipeline_vae_precision = "vae_precision"
elif component_name in ("audio_vae",):
pipeline_vae_config_attr = "audio_vae_config"
pipeline_vae_precision = "audio_vae_precision"
else:
raise ValueError(
f"Unsupported module name for VAE loader: {component_name}"
)
vae_config = getattr(server_args.pipeline_config, pipeline_vae_config_attr)
vae_precision = getattr(server_args.pipeline_config, pipeline_vae_precision)
resolved_vae_dtype = resolve_component_precision(server_args, component_name)
vae_dtype = (
resolved_vae_dtype
if resolved_vae_dtype is not None
else PRECISION_TO_TYPE[vae_precision]
)
vae_config.update_model_arch(config)
if hasattr(vae_config, "post_init"):
# NOTE: some post init logics are only available after updated with config
vae_config.post_init()
should_offload = self.should_offload(server_args)
target_device = self.target_device(should_offload)
# Check for auto_map first (custom VAE classes)
auto_map = config.get("auto_map", {})
auto_model_map = auto_map.get("AutoModel")
if auto_model_map:
module_path, cls_name = auto_model_map.rsplit(".", 1)
custom_module_file = os.path.join(component_model_path, f"{module_path}.py")
spec = importlib.util.spec_from_file_location("_custom", custom_module_file)
custom_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(custom_module)
vae_cls = getattr(custom_module, cls_name)
with set_default_torch_dtype(vae_dtype):
vae = vae_cls.from_pretrained(
component_model_path,
revision=server_args.revision,
trust_remote_code=server_args.trust_remote_code,
)
vae = vae.to(device=target_device, dtype=vae_dtype)
if _should_use_channels_last_3d(server_args, component_name):
n = _convert_conv3d_weights_to_channels_last_3d(vae)
if n > 0:
logger.info(
"VAE: converted %d Conv3d weights to channels_last_3d", n
)
vae = current_platform.optimize_vae(vae)
return vae
# Load from ModelRegistry (standard VAE classes)
with (
set_default_torch_dtype(vae_dtype),
skip_init_modules(),
):
vae_cls, _ = ModelRegistry.resolve_model_cls(class_name)
vae = vae_cls(vae_config).to(target_device)
safetensors_list = _list_safetensors_files(component_model_path)
safetensors_list = server_args.pipeline_config.select_vae_weight_files(
safetensors_list=safetensors_list,
component_model_path=component_model_path,
component_name=component_name,
vae_precision=vae_precision,
)
assert (
len(safetensors_list) >= 1
), f"Found no safetensors files in {component_model_path}"
loaded = {}
for sf_path in safetensors_list:
loaded.update(safetensors_load_file(sf_path))
_backfill_ltx2_audio_vae_latent_stats(loaded, component_name)
vae.load_state_dict(loaded, strict=False)
state_keys = set(vae.state_dict().keys())
loaded_keys = set(loaded.keys())
missing_keys = sorted(state_keys - loaded_keys)
unexpected_keys = sorted(loaded_keys - state_keys)
if missing_keys:
logger.warning("VAE missing keys: %s", missing_keys)
if unexpected_keys:
logger.warning("VAE unexpected keys: %s", unexpected_keys)
if _should_use_channels_last_3d(server_args, component_name):
n = _convert_conv3d_weights_to_channels_last_3d(vae)
if n > 0:
logger.info("VAE: converted %d Conv3d weights to channels_last_3d", n)
vae = current_platform.optimize_vae(vae)
return vae
@@ -0,0 +1,72 @@
import logging
from typing import Any
import requests
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import get_hf_config
logger = logging.getLogger(__name__)
class VisionLanguageEncoderLoader(ComponentLoader):
"""Loader for vision language encoder (typically Causal LM or Vision2Seq)."""
component_names = ["vision_language_encoder"]
expected_library = "transformers"
def load_customized(
self,
component_model_path: str,
server_args: ServerArgs,
transformers_or_diffusers: str = "vision_language_encoder",
) -> Any:
if transformers_or_diffusers == "vision_language_encoder":
if server_args.srt_encoder_url is not None:
health_url = server_args.srt_encoder_url.rstrip("/") + "/health"
try:
logger.info(f"Checking AR encoder server health at: {health_url}")
response = requests.get(
health_url, timeout=server_args.srt_encoder_connect_timeout
)
if response.status_code != 200:
error_msg = (
f"AR encoder server returned unhealthy status code: {response.status_code}. "
f"Please ensure the server at {server_args.srt_encoder_url} is fully initialized and compatible."
)
logger.error(error_msg)
raise RuntimeError(error_msg)
logger.info("Successfully connected to AR encoder server.")
except requests.RequestException as e:
error_msg = (
f"Failed to reach AR encoder server at {server_args.srt_encoder_url}. "
f"Error: {e}."
)
logger.error(error_msg)
raise RuntimeError(error_msg) from e
return server_args.srt_encoder_url
from transformers import GlmImageForConditionalGeneration
config = get_hf_config(
component_model_path,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
)
model = GlmImageForConditionalGeneration.from_pretrained(
component_model_path,
config=config,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.revision,
).to(get_local_torch_device())
return model
else:
raise ValueError(
f"Unsupported library for VisionLanguageEncoder: {transformers_or_diffusers}"
)
@@ -0,0 +1,90 @@
from safetensors.torch import load_file as safetensors_load_file
from sglang.multimodal_gen.configs.models import ModelConfig
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
ComponentLoader,
)
from sglang.multimodal_gen.runtime.loader.utils import (
_list_safetensors_files,
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
from sglang.multimodal_gen.runtime.server_args import ServerArgs
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
get_diffusers_component_config,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.utils.precision import resolve_component_precision
from sglang.multimodal_gen.utils import PRECISION_TO_TYPE
logger = init_logger(__name__)
class VocoderLoader(ComponentLoader):
component_names = ["vocoder"]
expected_library = "diffusers"
def should_offload(
self, server_args: ServerArgs, model_config: ModelConfig | None = None
):
return server_args.vae_cpu_offload
def load_customized(
self, component_model_path: str, server_args: ServerArgs, component_name: str
):
config = get_diffusers_component_config(component_path=component_model_path)
class_name = config.pop("_class_name", None) or self.component_architecture
assert (
class_name is not None
), "Vocoder class name must be available from component config or pipeline config."
server_args.model_paths[component_name] = component_model_path
from sglang.multimodal_gen.configs.models.vocoder.ltx_vocoder import (
LTXVocoderConfig,
)
vocoder_config = LTXVocoderConfig()
vocoder_config.update_model_arch(config)
resolved_vocoder_dtype = resolve_component_precision(server_args, "vocoder")
vocoder_dtype = (
resolved_vocoder_dtype
if resolved_vocoder_dtype is not None
else PRECISION_TO_TYPE["fp32"]
)
should_offload = self.should_offload(server_args)
target_device = self.target_device(should_offload)
with set_default_torch_dtype(vocoder_dtype), skip_init_modules():
vocoder_cls, _ = ModelRegistry.resolve_model_cls(class_name)
vocoder = vocoder_cls(vocoder_config).to(target_device)
safetensors_list = _list_safetensors_files(component_model_path)
assert (
len(safetensors_list) == 1
), f"Found {len(safetensors_list)} safetensors files in {component_model_path}"
loaded = safetensors_load_file(safetensors_list[0])
incompatible = vocoder.load_state_dict(loaded, strict=False)
missing_keys = []
unexpected_keys = []
try:
missing_keys = incompatible.missing_keys
unexpected_keys = incompatible.unexpected_keys
except AttributeError:
# Best-effort fallback in case older torch returns a tuple-like.
try:
missing_keys = incompatible[0]
unexpected_keys = incompatible[1]
except Exception:
pass
if missing_keys or unexpected_keys:
logger.warning(
"Loaded vocoder with missing_keys=%d unexpected_keys=%d",
len(missing_keys),
len(unexpected_keys),
)
return vocoder