94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
408 lines
16 KiB
Python
408 lines
16 KiB
Python
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import os
|
|
import re
|
|
from collections.abc import Generator
|
|
from itertools import chain
|
|
from typing import Any
|
|
|
|
import torch
|
|
from torch.distributed import init_device_mesh
|
|
from torch.distributed.fsdp import MixedPrecisionPolicy
|
|
|
|
from sglang.multimodal_gen.configs.models.dits.zimage import ZImageDitConfig
|
|
from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
|
from sglang.multimodal_gen.runtime.loader.fsdp_load import (
|
|
load_model_from_full_model_state_dict,
|
|
shard_model,
|
|
)
|
|
from sglang.multimodal_gen.runtime.loader.utils import (
|
|
get_param_names_mapping,
|
|
set_default_torch_dtype,
|
|
)
|
|
from sglang.multimodal_gen.runtime.loader.weight_utils import (
|
|
safetensors_weights_iterator,
|
|
)
|
|
from sglang.multimodal_gen.runtime.models.registry import ModelRegistry
|
|
from sglang.multimodal_gen.runtime.models.schedulers.scheduling_comfyui_passthrough import (
|
|
ComfyUIPassThroughScheduler,
|
|
)
|
|
from sglang.multimodal_gen.runtime.pipelines_core import LoRAPipeline
|
|
from sglang.multimodal_gen.runtime.pipelines_core.composed_pipeline_base import (
|
|
ComposedPipelineBase,
|
|
)
|
|
from sglang.multimodal_gen.runtime.pipelines_core.stages import (
|
|
ComfyUILatentPreparationStage,
|
|
DenoisingStage,
|
|
)
|
|
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|
from sglang.multimodal_gen.runtime.utils.precision import resolve_precision
|
|
from sglang.multimodal_gen.utils import set_mixed_precision_policy
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
class ComfyUIZImagePipeline(LoRAPipeline, ComposedPipelineBase):
|
|
"""
|
|
Simplified pipeline for ComfyUI integration with only denoising stage.
|
|
|
|
This pipeline requires pre-processed inputs:
|
|
- prompt_embeds: Pre-encoded text embeddings (list of tensors)
|
|
- negative_prompt_embeds: Pre-encoded negative prompt embeddings (if using CFG)
|
|
- latents: Optional initial noise latents (will be generated if not provided)
|
|
|
|
Usage:
|
|
generator = DiffGenerator.from_pretrained(
|
|
model_path="path/to/model",
|
|
pipeline_class_name="ComfyUIZImagePipeline",
|
|
device="cuda",
|
|
)
|
|
"""
|
|
|
|
pipeline_name = "ComfyUIZImagePipeline"
|
|
from sglang.multimodal_gen.configs.pipeline_configs.zimage import (
|
|
ZImagePipelineConfig,
|
|
)
|
|
from sglang.multimodal_gen.configs.sample.zimage import ZImageSamplingParams
|
|
|
|
pipeline_config_cls = ZImagePipelineConfig
|
|
sampling_params_cls = ZImageSamplingParams
|
|
|
|
_required_config_modules = [
|
|
"transformer",
|
|
"scheduler",
|
|
]
|
|
|
|
def initialize_pipeline(self, server_args: ServerArgs):
|
|
"""
|
|
Initialize the pipeline with ComfyUI pass-through scheduler.
|
|
This scheduler does not modify latents, allowing ComfyUI to handle denoising.
|
|
"""
|
|
self.modules["scheduler"] = ComfyUIPassThroughScheduler(
|
|
num_train_timesteps=1000
|
|
)
|
|
|
|
# Ensure VAE config is properly initialized even though we don't load the VAE model
|
|
# This is necessary because get_freqs_cis uses spatial_compression_ratio
|
|
if hasattr(server_args.pipeline_config, "vae_config"):
|
|
vae_config = server_args.pipeline_config.vae_config
|
|
if hasattr(vae_config, "post_init") and not hasattr(
|
|
vae_config, "_post_init_called"
|
|
):
|
|
vae_config.post_init()
|
|
logger.info(
|
|
"Called vae_config.post_init() to set spatial_compression_ratio. "
|
|
f"spatial_compression_ratio={vae_config.arch_config.spatial_compression_ratio}"
|
|
)
|
|
|
|
def load_modules(
|
|
self,
|
|
server_args: ServerArgs,
|
|
loaded_modules: dict[str, torch.nn.Module] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Load modules for ComfyUIZImagePipeline.
|
|
|
|
If model_path is a safetensors file, load transformer directly from it
|
|
without requiring model_index.json. Otherwise, fall back to default loading.
|
|
"""
|
|
if os.path.isfile(self.model_path) and self.model_path.endswith(".safetensors"):
|
|
logger.info(
|
|
"Detected safetensors file, loading transformer directly from: %s",
|
|
self.model_path,
|
|
)
|
|
return self._load_transformer_from_safetensors(server_args, loaded_modules)
|
|
else:
|
|
logger.info(
|
|
"Model path is a directory, using default loading method: %s",
|
|
self.model_path,
|
|
)
|
|
return super().load_modules(server_args, loaded_modules)
|
|
|
|
def _convert_comfyui_qkv_weights(
|
|
self,
|
|
weight_iterator: Generator[tuple[str, torch.Tensor], None, None],
|
|
dim: int,
|
|
num_heads: int,
|
|
num_kv_heads: int,
|
|
) -> Generator[tuple[str, torch.Tensor], None, None]:
|
|
"""
|
|
Convert ComfyUI zimage qkv weights to SGLang format.
|
|
Splits merged qkv.weight into separate to_q, to_k, to_v weights.
|
|
|
|
Args:
|
|
weight_iterator: Iterator yielding (name, tensor) pairs from safetensors
|
|
dim: Model dimension
|
|
num_heads: Number of attention heads
|
|
num_kv_heads: Number of key-value heads
|
|
|
|
Yields:
|
|
(name, tensor) pairs with qkv weights split into to_q, to_k, to_v
|
|
"""
|
|
head_dim = dim // num_heads
|
|
q_size = dim
|
|
k_size = head_dim * num_kv_heads
|
|
|
|
for name, tensor in weight_iterator:
|
|
# Match qkv weights in layers, noise_refiner, or context_refiner
|
|
# Pattern: (layers|noise_refiner|context_refiner).{i}.attention.qkv.(weight|bias)
|
|
match = re.match(
|
|
r"(layers|noise_refiner|context_refiner)\.(\d+)\.attention\.qkv\.(weight|bias)$",
|
|
name,
|
|
)
|
|
if match:
|
|
module_name, layer_idx, param_type = match.groups()
|
|
base_name = f"{module_name}.{layer_idx}.attention"
|
|
|
|
if param_type == "weight":
|
|
# Weight shape: (q_size + k_size + v_size, dim)
|
|
# Split into q, k, v
|
|
q_weight = tensor[:q_size, :]
|
|
k_weight = tensor[q_size : q_size + k_size, :]
|
|
v_weight = tensor[q_size + k_size :, :]
|
|
|
|
logger.debug(
|
|
f"Splitting {name} (shape {tensor.shape}) into "
|
|
f"to_q ({q_weight.shape}), to_k ({k_weight.shape}), to_v ({v_weight.shape})"
|
|
)
|
|
|
|
yield f"{base_name}.to_q.weight", q_weight
|
|
yield f"{base_name}.to_k.weight", k_weight
|
|
yield f"{base_name}.to_v.weight", v_weight
|
|
else: # bias
|
|
# Bias shape: (q_size + k_size + v_size,)
|
|
# Split into q, k, v
|
|
q_bias = tensor[:q_size]
|
|
k_bias = tensor[q_size : q_size + k_size]
|
|
v_bias = tensor[q_size + k_size :]
|
|
|
|
logger.debug(
|
|
f"Splitting {name} (shape {tensor.shape}) into "
|
|
f"to_q ({q_bias.shape}), to_k ({k_bias.shape}), to_v ({v_bias.shape})"
|
|
)
|
|
|
|
yield f"{base_name}.to_q.bias", q_bias
|
|
yield f"{base_name}.to_k.bias", k_bias
|
|
yield f"{base_name}.to_v.bias", v_bias
|
|
else:
|
|
# Pass through other weights unchanged
|
|
yield name, tensor
|
|
|
|
def _load_transformer_from_safetensors(
|
|
self,
|
|
server_args: ServerArgs,
|
|
loaded_modules: dict[str, torch.nn.Module] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Load transformer directly from safetensors file without model_index.json.
|
|
|
|
This method:
|
|
1. Uses hardcoded ZImageDitConfig for zimage model
|
|
2. Loads transformer from the safetensors file
|
|
3. Uses ComfyUIPassThroughScheduler (already created in initialize_pipeline)
|
|
"""
|
|
# Check if transformer is already provided
|
|
if loaded_modules is not None and "transformer" in loaded_modules:
|
|
logger.info("Using provided transformer module")
|
|
components = {
|
|
"transformer": loaded_modules["transformer"],
|
|
"scheduler": self.modules.get("scheduler"),
|
|
}
|
|
return components
|
|
|
|
if hasattr(server_args.pipeline_config, "dit_config"):
|
|
dit_config = server_args.pipeline_config.dit_config
|
|
if not isinstance(dit_config, ZImageDitConfig):
|
|
logger.warning(
|
|
"dit_config is not ZImageDitConfig, creating new ZImageDitConfig"
|
|
)
|
|
dit_config = ZImageDitConfig()
|
|
server_args.pipeline_config.dit_config = dit_config
|
|
else:
|
|
logger.info("Creating default ZImageDitConfig")
|
|
dit_config = ZImageDitConfig()
|
|
server_args.pipeline_config.dit_config = dit_config
|
|
|
|
if dit_config.arch_config.param_names_mapping is None:
|
|
dit_config.arch_config.param_names_mapping = {}
|
|
|
|
# Add mappings for norm layers: map from ComfyUI format (k_norm/q_norm) to SGLang format (norm_k/norm_q)
|
|
# The regex matches the source name from safetensors, and the tuple specifies the target name in the model
|
|
# Note: qkv weights are handled separately by _convert_comfyui_qkv_weights function
|
|
comfyui_norm_mappings = {
|
|
r"(.*)\.attention\.k_norm\.weight$": (
|
|
r"\1.attention.norm_k.weight",
|
|
None,
|
|
None,
|
|
),
|
|
r"(.*)\.attention\.q_norm\.weight$": (
|
|
r"\1.attention.norm_q.weight",
|
|
None,
|
|
None,
|
|
),
|
|
r"(.*)\.attention\.out\.weight$": (
|
|
r"\1.attention.to_out.0.weight",
|
|
None,
|
|
None,
|
|
),
|
|
r"^final_layer\.(.*)$": (r"all_final_layer.2-1.\1", None, None),
|
|
r"^x_embedder\.(.*)$": (r"all_x_embedder.2-1.\1", None, None),
|
|
}
|
|
|
|
# Merge ComfyUI mappings with existing mappings (ComfyUI mappings take precedence)
|
|
updated_mapping = {
|
|
**dit_config.arch_config.param_names_mapping,
|
|
**comfyui_norm_mappings,
|
|
}
|
|
dit_config.arch_config.param_names_mapping = updated_mapping
|
|
logger.info(
|
|
"Added ComfyUI weight name mappings (k_norm/q_norm -> norm_k/norm_q) to param_names_mapping. "
|
|
f"Total mappings: {len(updated_mapping)}"
|
|
)
|
|
|
|
cls_name = "ZImageTransformer2DModel"
|
|
model_cls, _ = ModelRegistry.resolve_model_cls(cls_name)
|
|
logger.info("Resolved transformer class: %s", cls_name)
|
|
safetensors_list = [self.model_path]
|
|
logger.info("Loading weights from: %s", safetensors_list)
|
|
|
|
default_dtype = resolve_precision(
|
|
server_args, "dit", precision_attr="dit_precision"
|
|
)
|
|
server_args.model_paths["transformer"] = os.path.dirname(self.model_path) or "."
|
|
hf_config = {}
|
|
|
|
assert server_args.hsdp_shard_dim is not None, "hsdp_shard_dim must be set"
|
|
logger.info(
|
|
"Loading %s from safetensors file, default_dtype: %s",
|
|
cls_name,
|
|
default_dtype,
|
|
)
|
|
|
|
original_mapping = model_cls.param_names_mapping
|
|
model_cls.param_names_mapping = updated_mapping
|
|
logger.info(
|
|
"Temporarily updated model class param_names_mapping with ComfyUI mappings. "
|
|
f"Total mappings: {len(updated_mapping)}"
|
|
)
|
|
|
|
try:
|
|
# Create model first (same as maybe_load_fsdp_model)
|
|
from sglang.multimodal_gen.runtime.platforms import current_platform
|
|
|
|
# precision-constraint: FSDP mixed precision currently uses bf16
|
|
# parameters and fp32 reduction regardless of model load dtype.
|
|
mp_policy = MixedPrecisionPolicy(
|
|
torch.bfloat16, torch.float32, None, cast_forward_inputs=False
|
|
)
|
|
|
|
set_mixed_precision_policy(
|
|
param_dtype=torch.bfloat16,
|
|
reduce_dtype=torch.float32,
|
|
output_dtype=None,
|
|
mp_policy=mp_policy,
|
|
)
|
|
|
|
with set_default_torch_dtype(default_dtype), torch.device("meta"):
|
|
model = model_cls(**{"config": dit_config, "hf_config": hf_config})
|
|
|
|
# Check if we should use FSDP
|
|
use_fsdp = server_args.use_fsdp_inference
|
|
if current_platform.is_mps():
|
|
use_fsdp = False
|
|
logger.info("Disabling FSDP for MPS platform as it's not compatible")
|
|
|
|
if use_fsdp:
|
|
device_mesh = init_device_mesh(
|
|
current_platform.device_type,
|
|
mesh_shape=(
|
|
server_args.hsdp_replicate_dim,
|
|
server_args.hsdp_shard_dim,
|
|
),
|
|
mesh_dim_names=("replicate", "shard"),
|
|
)
|
|
shard_model(
|
|
model,
|
|
cpu_offload=server_args.dit_cpu_offload,
|
|
reshard_after_forward=True,
|
|
mp_policy=mp_policy,
|
|
mesh=device_mesh,
|
|
fsdp_shard_conditions=getattr(
|
|
model, "_fsdp_shard_conditions", None
|
|
),
|
|
pin_cpu_memory=server_args.pin_cpu_memory,
|
|
)
|
|
|
|
# Get model dimensions for qkv splitting
|
|
arch_config = dit_config.arch_config
|
|
dim = arch_config.dim
|
|
num_heads = arch_config.num_attention_heads
|
|
num_kv_heads = arch_config.n_kv_heads
|
|
|
|
# Create weight iterator with qkv conversion
|
|
base_weight_iterator = safetensors_weights_iterator(safetensors_list)
|
|
converted_weight_iterator = self._convert_comfyui_qkv_weights(
|
|
base_weight_iterator, dim, num_heads, num_kv_heads
|
|
)
|
|
|
|
# Load weights
|
|
param_names_mapping_fn = get_param_names_mapping(updated_mapping)
|
|
load_model_from_full_model_state_dict(
|
|
model,
|
|
converted_weight_iterator,
|
|
get_local_torch_device(),
|
|
default_dtype,
|
|
strict=True,
|
|
cpu_offload=server_args.dit_cpu_offload,
|
|
param_names_mapping=param_names_mapping_fn,
|
|
)
|
|
|
|
# Check for meta parameters
|
|
for n, p in chain(model.named_parameters(), model.named_buffers()):
|
|
if p.is_meta:
|
|
raise RuntimeError(
|
|
f"Unexpected param or buffer {n} on meta device."
|
|
)
|
|
if isinstance(p, torch.nn.Parameter):
|
|
p.requires_grad = False
|
|
finally:
|
|
model_cls.param_names_mapping = original_mapping
|
|
|
|
total_params = sum(p.numel() for p in model.parameters())
|
|
logger.info("Loaded transformer with %.2fB parameters", total_params / 1e9)
|
|
|
|
components = {
|
|
"transformer": model,
|
|
"scheduler": self.modules.get("scheduler"),
|
|
}
|
|
|
|
logger.info("Successfully loaded modules: %s", list(components.keys()))
|
|
return components
|
|
|
|
def create_pipeline_stages(self, server_args: ServerArgs):
|
|
logger.info(
|
|
"ComfyUIZImagePipeline.create_pipeline_stages() called - creating latent_preparation_stage and denoising_stage"
|
|
)
|
|
|
|
self.add_stages(
|
|
[
|
|
ComfyUILatentPreparationStage(
|
|
scheduler=self.get_module("scheduler"),
|
|
transformer=self.get_module("transformer"),
|
|
),
|
|
DenoisingStage(
|
|
transformer=self.get_module("transformer"),
|
|
scheduler=self.get_module("scheduler"),
|
|
),
|
|
]
|
|
)
|
|
|
|
logger.info(
|
|
f"ComfyUIZImagePipeline stages created: {list(self._stage_name_mapping.keys())}"
|
|
)
|
|
|
|
|
|
EntryClass = ComfyUIZImagePipeline
|