Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

658 lines
25 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Unit tests for disaggregation role-based module filtering."""
import unittest
from types import SimpleNamespace
from unittest.mock import patch
import torch
from sglang.multimodal_gen.configs.pipeline_configs.hunyuan3d import (
Hunyuan3D2PipelineConfig,
)
from sglang.multimodal_gen.runtime import server_args as server_args_module
from sglang.multimodal_gen.runtime.disaggregation.roles import (
RoleType,
filter_modules_for_role,
get_module_role,
)
from sglang.multimodal_gen.runtime.pipelines.flux_2 import Flux2Pipeline
from sglang.multimodal_gen.runtime.pipelines.glm_image import GlmImagePipeline
from sglang.multimodal_gen.runtime.pipelines.hunyuan3d_pipeline import (
Hunyuan3D2Pipeline,
)
from sglang.multimodal_gen.runtime.pipelines.ltx_2_pipeline import LTX2Pipeline
from sglang.multimodal_gen.runtime.pipelines.mova_pipeline import (
MOVAPipeline,
MOVAPipelineAlias,
)
from sglang.multimodal_gen.runtime.pipelines.qwen_image import (
QwenImageEditPipeline,
QwenImageLayeredPipeline,
)
from sglang.multimodal_gen.runtime.pipelines.wan_i2v_dmd_pipeline import (
WanImageToVideoDmdPipeline,
)
from sglang.multimodal_gen.runtime.pipelines.wan_i2v_pipeline import (
WanImageToVideoPipeline,
)
from sglang.multimodal_gen.runtime.pipelines_core.composed_pipeline_base import (
ComposedPipelineBase,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.image_encoding import (
ImageVAEEncodingStage,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.helios_denoising import (
HeliosChunkedDenoisingStage,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.hunyuan3d.shape import (
Hunyuan3DShapeBeforeDenoisingStage,
Hunyuan3DShapeExportStage,
Hunyuan3DShapeSaveStage,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.ltx_2.denoising_av import (
LTX2RefinementStage,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.mova import (
MOVADecodingStage,
MOVADenoisingStage,
)
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.qwen_image_layered import (
QwenImageLayeredBeforeDenoisingStage,
_resolve_layered_image_path,
_resolve_text_encoder_dtype,
)
from sglang.multimodal_gen.runtime.server_args import set_global_server_args
class _GlobalStageArgsMixin:
def _install_stage_server_args(self, **kwargs):
server_args = SimpleNamespace(
comfyui_mode=False,
enable_torch_compile=False,
enable_breakable_cuda_graph=False,
enable_cfg_parallel=False,
attention_backend=None,
**kwargs,
)
set_global_server_args(server_args)
return server_args
def setUp(self):
super().setUp()
self._prev_global_server_args = server_args_module._global_server_args
self._install_stage_server_args()
def tearDown(self):
set_global_server_args(self._prev_global_server_args)
super().tearDown()
class TestRoleType(unittest.TestCase):
def test_from_string(self):
self.assertEqual(RoleType.from_string("monolithic"), RoleType.MONOLITHIC)
self.assertEqual(RoleType.from_string("encoder"), RoleType.ENCODER)
self.assertEqual(RoleType.from_string("denoiser"), RoleType.DENOISER)
self.assertEqual(RoleType.from_string("decoder"), RoleType.DECODER)
self.assertEqual(RoleType.from_string("ENCODER"), RoleType.ENCODER)
def test_from_string_backward_compat(self):
self.assertEqual(RoleType.from_string("denoising"), RoleType.DENOISER)
def test_from_string_invalid(self):
with self.assertRaises(ValueError):
RoleType.from_string("invalid")
def test_choices(self):
choices = RoleType.choices()
self.assertIn("monolithic", choices)
self.assertIn("encoder", choices)
self.assertIn("denoiser", choices)
self.assertIn("denoising", choices)
self.assertIn("decoder", choices)
class TestGetModuleRole(unittest.TestCase):
def test_encoder_modules(self):
self.assertEqual(get_module_role("text_encoder"), RoleType.ENCODER)
self.assertEqual(get_module_role("text_encoder_2"), RoleType.ENCODER)
self.assertEqual(get_module_role("tokenizer"), RoleType.ENCODER)
self.assertEqual(get_module_role("tokenizer_2"), RoleType.ENCODER)
self.assertEqual(get_module_role("image_encoder"), RoleType.ENCODER)
self.assertEqual(get_module_role("image_processor"), RoleType.ENCODER)
self.assertEqual(get_module_role("connectors"), RoleType.ENCODER)
self.assertEqual(get_module_role("vision_language_encoder"), RoleType.ENCODER)
self.assertEqual(get_module_role("hy3dshape_conditioner"), RoleType.ENCODER)
self.assertEqual(get_module_role("hy3dshape_image_processor"), RoleType.ENCODER)
def test_denoiser_modules(self):
self.assertEqual(get_module_role("transformer"), RoleType.DENOISER)
self.assertEqual(get_module_role("transformer_2"), RoleType.DENOISER)
self.assertEqual(get_module_role("video_dit"), RoleType.DENOISER)
self.assertEqual(get_module_role("video_dit_2"), RoleType.DENOISER)
self.assertEqual(get_module_role("audio_dit"), RoleType.DENOISER)
self.assertEqual(get_module_role("dual_tower_bridge"), RoleType.DENOISER)
self.assertEqual(get_module_role("hy3dshape_model"), RoleType.DENOISER)
def test_decoder_modules(self):
self.assertEqual(get_module_role("vae"), RoleType.DECODER)
self.assertEqual(get_module_role("audio_vae"), RoleType.DECODER)
self.assertEqual(get_module_role("video_vae"), RoleType.DECODER)
self.assertEqual(get_module_role("vocoder"), RoleType.DECODER)
self.assertEqual(get_module_role("hy3dshape_vae"), RoleType.DECODER)
def test_shared_modules(self):
self.assertIsNone(get_module_role("scheduler"))
self.assertIsNone(get_module_role("hy3dshape_scheduler"))
class TestFilterModulesForRole(unittest.TestCase):
WAN_MODULES = ["text_encoder", "tokenizer", "vae", "transformer", "scheduler"]
def test_monolithic_keeps_all(self):
result = filter_modules_for_role(self.WAN_MODULES, RoleType.MONOLITHIC)
self.assertEqual(result, self.WAN_MODULES)
def test_encoder_does_not_keep_decoder_modules_by_default(self):
result = filter_modules_for_role(self.WAN_MODULES, RoleType.ENCODER)
self.assertEqual(result, ["text_encoder", "tokenizer", "scheduler"])
def test_encoder_can_keep_explicit_cross_role_modules(self):
result = filter_modules_for_role(
self.WAN_MODULES,
RoleType.ENCODER,
extra_allowed_modules={"vae"},
)
self.assertEqual(result, ["text_encoder", "tokenizer", "vae", "scheduler"])
def test_denoiser_skips_encoders_and_vae(self):
result = filter_modules_for_role(self.WAN_MODULES, RoleType.DENOISER)
self.assertEqual(result, ["transformer", "scheduler"])
def test_decoder_keeps_vae_and_scheduler(self):
result = filter_modules_for_role(self.WAN_MODULES, RoleType.DECODER)
self.assertEqual(result, ["vae", "scheduler"])
class TestFilterModulesLTX2(unittest.TestCase):
LTX2_MODULES = [
"transformer",
"text_encoder",
"tokenizer",
"scheduler",
"vae",
"audio_vae",
"vocoder",
"connectors",
]
def test_decoder_includes_audio(self):
result = filter_modules_for_role(self.LTX2_MODULES, RoleType.DECODER)
self.assertEqual(result, ["scheduler", "vae", "audio_vae", "vocoder"])
def test_encoder_does_not_keep_decoder_modules_by_default(self):
result = filter_modules_for_role(self.LTX2_MODULES, RoleType.ENCODER)
self.assertEqual(
result, ["text_encoder", "tokenizer", "scheduler", "connectors"]
)
def test_denoiser_can_keep_ti2v_decoder_components(self):
result = filter_modules_for_role(
self.LTX2_MODULES,
RoleType.DENOISER,
extra_allowed_modules={"vae", "audio_vae"},
)
self.assertEqual(result, ["transformer", "scheduler", "vae", "audio_vae"])
# Consolidated from test_pipeline_stage_role_filter.py.
class _FakePipeline(ComposedPipelineBase):
pipeline_name = "FakePipeline"
_required_config_modules = []
def initialize_pipeline(self, server_args):
pass
def create_pipeline_stages(self, server_args) -> None:
pass
def _make_pipeline(role: RoleType) -> _FakePipeline:
pipeline = object.__new__(_FakePipeline)
pipeline.modules = {}
pipeline._stages = []
pipeline._stage_name_mapping = {}
pipeline._disagg_role = role
return pipeline
class _FakeStage:
def __init__(self, role_affinity: RoleType):
self.role_affinity = role_affinity
self.registered_stage_name = None
self.profile_stage_name = None
def set_registered_stage_name(self, stage_name: str) -> None:
self.registered_stage_name = stage_name
def set_profile_stage_name(self, stage_name: str) -> None:
self.profile_stage_name = stage_name
class TestPipelineStageRoleFilter(unittest.TestCase):
def test_stage_factory_skips_without_constructing_for_other_role(self):
pipeline = _make_pipeline(RoleType.ENCODER)
def should_not_construct():
raise AssertionError("stage factory should have been skipped")
pipeline.add_stage_factory(
RoleType.DENOISER,
should_not_construct,
"denoising_stage",
)
self.assertEqual(pipeline.stages, [])
def test_stage_factory_constructs_for_matching_role(self):
pipeline = _make_pipeline(RoleType.DENOISER)
stage = _FakeStage(RoleType.DENOISER)
events = []
def create_stage():
events.append("called")
return stage
pipeline.add_stage_factory(
RoleType.DENOISER,
create_stage,
"denoising_stage",
)
self.assertEqual(events, ["called"])
self.assertIs(pipeline.get_stage("denoising_stage"), stage)
self.assertEqual(stage.registered_stage_name, "denoising_stage")
def test_encoder_role_does_not_construct_standard_denoising_stage(self):
pipeline = _make_pipeline(RoleType.ENCODER)
with patch(
"sglang.multimodal_gen.runtime.pipelines_core.composed_pipeline_base.DenoisingStage",
side_effect=AssertionError("DenoisingStage should not be constructed"),
):
pipeline.add_standard_denoising_stage()
self.assertEqual(pipeline.stages, [])
def test_encoder_role_does_not_construct_standard_decoding_stage(self):
pipeline = _make_pipeline(RoleType.ENCODER)
with patch(
"sglang.multimodal_gen.runtime.pipelines_core.composed_pipeline_base.DecodingStage",
side_effect=AssertionError("DecodingStage should not be constructed"),
):
pipeline.add_standard_decoding_stage()
self.assertEqual(pipeline.stages, [])
# Consolidated from test_disagg_pipeline_alignment.py.
class TestPipelineSpecificExtraModules(unittest.TestCase):
def _get_extra_modules(
self, pipeline_cls, role: RoleType, task_name: str
) -> set[str]:
pipeline = object.__new__(pipeline_cls)
return pipeline._get_extra_allowed_modules_for_role(role, task_name)
def test_flux_encoder_keeps_vae(self):
extras = self._get_extra_modules(Flux2Pipeline, RoleType.ENCODER, "ti2i")
filtered = filter_modules_for_role(
Flux2Pipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae"})
self.assertEqual(
set(filtered), {"text_encoder", "tokenizer", "vae", "scheduler"}
)
def test_qwen_image_edit_encoder_keeps_vae(self):
extras = self._get_extra_modules(
QwenImageEditPipeline, RoleType.ENCODER, "ti2i"
)
filtered = filter_modules_for_role(
QwenImageEditPipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae"})
self.assertEqual(
set(filtered),
{"processor", "scheduler", "text_encoder", "tokenizer", "vae"},
)
def test_qwen_image_layered_encoder_keeps_required_cross_role_modules(self):
extras = self._get_extra_modules(
QwenImageLayeredPipeline, RoleType.ENCODER, "ti2i"
)
filtered = filter_modules_for_role(
QwenImageLayeredPipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae", "transformer"})
self.assertNotIn(
"text_encoder", QwenImageLayeredPipeline._required_config_modules
)
self.assertEqual(
set(filtered),
{
"vae",
"tokenizer",
"processor",
"transformer",
"scheduler",
},
)
def test_glm_image_encoder_keeps_vae_and_transformer(self):
extras = self._get_extra_modules(GlmImagePipeline, RoleType.ENCODER, "ti2i")
filtered = filter_modules_for_role(
GlmImagePipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae", "transformer"})
self.assertEqual(
set(filtered),
{
"text_encoder",
"tokenizer",
"vae",
"vision_language_encoder",
"processor",
"transformer",
"scheduler",
},
)
def test_wan_ti2v_denoiser_keeps_vae(self):
for pipeline_cls in (WanImageToVideoPipeline, WanImageToVideoDmdPipeline):
extras = self._get_extra_modules(pipeline_cls, RoleType.DENOISER, "ti2v")
filtered = filter_modules_for_role(
pipeline_cls._required_config_modules,
RoleType.DENOISER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae"})
self.assertEqual(set(filtered), {"vae", "transformer", "scheduler"})
def test_ltx2_encoder_does_not_keep_decoder_modules(self):
extras = self._get_extra_modules(LTX2Pipeline, RoleType.ENCODER, "ti2v")
filtered = filter_modules_for_role(
LTX2Pipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, set())
self.assertEqual(
set(filtered),
{"text_encoder", "tokenizer", "scheduler", "connectors"},
)
def test_ltx2_ti2v_denoiser_keeps_vae_and_audio_vae(self):
extras = self._get_extra_modules(LTX2Pipeline, RoleType.DENOISER, "ti2v")
filtered = filter_modules_for_role(
LTX2Pipeline._required_config_modules,
RoleType.DENOISER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"vae", "audio_vae"})
self.assertEqual(
set(filtered), {"transformer", "scheduler", "vae", "audio_vae"}
)
def test_mova_encoder_keeps_video_and_audio_vaes(self):
extras = self._get_extra_modules(MOVAPipeline, RoleType.ENCODER, "i2v")
filtered = filter_modules_for_role(
MOVAPipeline._required_config_modules,
RoleType.ENCODER,
extra_allowed_modules=extras,
)
self.assertEqual(extras, {"video_vae", "audio_vae"})
self.assertEqual(
set(filtered),
{"video_vae", "audio_vae", "text_encoder", "tokenizer", "scheduler"},
)
def test_mova_alias_uses_same_encoder_extras(self):
extras = self._get_extra_modules(MOVAPipelineAlias, RoleType.ENCODER, "i2v")
self.assertEqual(extras, {"video_vae", "audio_vae"})
class TestQwenImageLayeredDtype(_GlobalStageArgsMixin, unittest.TestCase):
def test_layered_image_path_accepts_string_and_list(self):
self.assertEqual(
_resolve_layered_image_path("/tmp/input.png"),
"/tmp/input.png",
)
self.assertEqual(
_resolve_layered_image_path(["/tmp/input.png"]),
"/tmp/input.png",
)
def test_layered_image_path_rejects_empty_list(self):
with self.assertRaisesRegex(ValueError, "non-empty image_path"):
_resolve_layered_image_path([])
def test_text_encoder_dtype_uses_parameter_dtype_without_dtype_attr(self):
text_encoder = torch.nn.Linear(1, 1, bias=False).to(dtype=torch.bfloat16)
self.assertEqual(
_resolve_text_encoder_dtype(text_encoder),
torch.bfloat16,
)
def test_component_uses_keep_standard_text_encoder_and_configured_dtypes(self):
class _DummyVAE:
temperal_downsample = []
z_dim = 16
def to(self, *args, **kwargs):
return self
with patch(
"sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.qwen_image_layered.get_local_torch_device",
return_value=torch.device("cpu"),
):
stage = QwenImageLayeredBeforeDenoisingStage(
vae=_DummyVAE(),
text_encoder=torch.nn.Linear(1, 1),
tokenizer=object(),
processor=object(),
transformer=object(),
scheduler=object(),
model_path="/unused",
vae_dtype=torch.float32,
text_encoder_dtype=torch.float16,
)
uses = stage.component_uses(SimpleNamespace(), "qwen_layered")
self.assertEqual(
[(use.component_name, use.target_dtype) for use in uses],
[("text_encoder", torch.float16), ("vae", torch.float32)],
)
class TestImageVAEEncodingStageComponentName(_GlobalStageArgsMixin, unittest.TestCase):
def test_component_name_can_follow_non_default_vae_key(self):
stage = ImageVAEEncodingStage(vae=object(), component_name="video_vae")
server_args = SimpleNamespace(
pipeline_config=SimpleNamespace(vae_precision="bf16")
)
uses = stage.component_uses(server_args, "image_vae_encoding")
self.assertEqual(len(uses), 1)
self.assertEqual(uses[0].component_name, "video_vae")
self.assertEqual(uses[0].target_dtype, torch.bfloat16)
class TestStageAffinityAndValidation(_GlobalStageArgsMixin, unittest.TestCase):
def _make_hunyuan_pipeline(
self, role: RoleType, *, paint_enable: bool
) -> Hunyuan3D2Pipeline:
pipeline = object.__new__(Hunyuan3D2Pipeline)
pipeline.server_args = self._install_stage_server_args(
pipeline_config=Hunyuan3D2PipelineConfig(paint_enable=paint_enable)
)
pipeline._disagg_role = role
pipeline.modules = {
"hy3dshape_image_processor": object(),
"hy3dshape_conditioner": object(),
"hy3dshape_scheduler": object(),
"hy3dshape_model": torch.nn.Linear(1, 1),
"hy3dshape_vae": object(),
}
pipeline._stages = []
pipeline._stage_name_mapping = {}
return pipeline
def test_helios_denoising_stage_is_denoiser_affine(self):
stage = object.__new__(HeliosChunkedDenoisingStage)
self.assertEqual(stage.role_affinity, RoleType.DENOISER)
def test_mova_denoising_stage_is_denoiser_affine(self):
stage = object.__new__(MOVADenoisingStage)
self.assertEqual(stage.role_affinity, RoleType.DENOISER)
def test_mova_decoding_stage_is_decoder_affine(self):
stage = object.__new__(MOVADecodingStage)
self.assertEqual(stage.role_affinity, RoleType.DECODER)
def test_mova_skips_torch_compile_on_rocm(self):
class _CompileTrackingModule(torch.nn.Module):
def __init__(self):
super().__init__()
self.compile_called = False
def compile(self, *args, **kwargs):
self.compile_called = True
stage = object.__new__(MOVADenoisingStage)
module = _CompileTrackingModule()
server_args = SimpleNamespace(enable_torch_compile=True)
with patch(
"sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.mova.current_platform.is_hip",
return_value=True,
):
stage._maybe_enable_torch_compile(module, server_args)
self.assertFalse(module.compile_called)
def test_hunyuan3d_shape_only_disagg_accepts_non_monolithic_roles(self):
pipeline = self._make_hunyuan_pipeline(RoleType.ENCODER, paint_enable=False)
pipeline.validate_disagg_role(RoleType.ENCODER)
pipeline.validate_disagg_role(RoleType.MONOLITHIC)
def test_hunyuan3d_disagg_rejects_paint_pipeline(self):
pipeline = self._make_hunyuan_pipeline(RoleType.ENCODER, paint_enable=True)
with self.assertRaisesRegex(ValueError, "shape-only disaggregation"):
pipeline.validate_disagg_role(RoleType.ENCODER)
def test_hunyuan3d_shape_export_and_save_are_decoder_affine(self):
export_stage = Hunyuan3DShapeExportStage(
vae=object(),
config=Hunyuan3D2PipelineConfig(paint_enable=False),
)
save_stage = Hunyuan3DShapeSaveStage(
config=Hunyuan3D2PipelineConfig(paint_enable=False),
)
self.assertEqual(export_stage.role_affinity, RoleType.DECODER)
self.assertEqual(save_stage.role_affinity, RoleType.DECODER)
def test_hunyuan3d_stage_filtering_matches_shape_only_roles(self):
expected = {
RoleType.ENCODER: ["shape_before_denoising"],
RoleType.DENOISER: ["shape_denoising"],
RoleType.DECODER: ["shape_export", "shape_save"],
}
for role, stage_names in expected.items():
pipeline = self._make_hunyuan_pipeline(role, paint_enable=False)
pipeline.create_pipeline_stages(pipeline.server_args)
self.assertEqual(list(pipeline._stage_name_mapping.keys()), stage_names)
def test_hunyuan3d_shape_stage_no_longer_stores_model_dtype(self):
pipeline = self._make_hunyuan_pipeline(RoleType.ENCODER, paint_enable=False)
pipeline.create_pipeline_stages(pipeline.server_args)
stage = pipeline._stage_name_mapping["shape_before_denoising"]
self.assertIsInstance(stage, Hunyuan3DShapeBeforeDenoisingStage)
self.assertFalse(hasattr(stage, "model_dtype"))
def test_ltx2_refinement_stage_keeps_class_name_stage_key(self):
stage = object.__new__(LTX2RefinementStage)
self.assertEqual(
ComposedPipelineBase._infer_stage_name(stage), "LTX2RefinementStage"
)
class TestHunyuan3DShapeStageRuntimeDtype(_GlobalStageArgsMixin, unittest.TestCase):
def test_conditioner_parameter_dtype_wins_over_sample_dtype(self):
conditioner = torch.nn.Linear(4, 4, bias=False).to(dtype=torch.float32)
stage = Hunyuan3DShapeBeforeDenoisingStage(
image_processor=object(),
conditioner=conditioner,
scheduler=SimpleNamespace(init_noise_sigma=1.0),
config=Hunyuan3D2PipelineConfig(),
latent_shape=(1, 2, 2),
guidance_embed=False,
)
self.assertEqual(
stage._resolve_runtime_dtype(torch.zeros(1, dtype=torch.float16)),
torch.float32,
)
def test_runtime_dtype_falls_back_to_sample_tensor_without_module_dtype(self):
stage = Hunyuan3DShapeBeforeDenoisingStage(
image_processor=object(),
conditioner=object(),
scheduler=SimpleNamespace(init_noise_sigma=1.0),
config=Hunyuan3D2PipelineConfig(),
latent_shape=(1, 2, 2),
guidance_embed=False,
)
self.assertEqual(
stage._resolve_runtime_dtype(torch.zeros(1, dtype=torch.bfloat16)),
torch.bfloat16,
)
def test_runtime_dtype_warns_and_falls_back_after_non_iterable_parameters(self):
conditioner = SimpleNamespace(
parameters=lambda: (_ for _ in ()).throw(TypeError("not iterable")),
buffers=lambda: iter(()),
)
stage = Hunyuan3DShapeBeforeDenoisingStage(
image_processor=object(),
conditioner=conditioner,
scheduler=SimpleNamespace(init_noise_sigma=1.0),
config=Hunyuan3D2PipelineConfig(),
latent_shape=(1, 2, 2),
guidance_embed=False,
)
with patch(
"sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.hunyuan3d.shape.logger.warning"
) as mock_warning:
dtype = stage._resolve_runtime_dtype(torch.zeros(1, dtype=torch.float16))
self.assertEqual(dtype, torch.float16)
mock_warning.assert_called_once()
self.assertEqual(mock_warning.call_args.args[1], "parameters")
if __name__ == "__main__":
unittest.main()