[diffusion] chore: reuse SRT CLIP encoder blocks (#35004)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any, Literal
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
@@ -79,8 +79,8 @@ class EncoderConfig(ModelConfig):
|
|||||||
# Parallel folding: during the encoding stage the whole DiT replica is idle,
|
# Parallel folding: during the encoding stage the whole DiT replica is idle,
|
||||||
# so TP-shard the encoder across those otherwise-unused GPUs instead of
|
# so TP-shard the encoder across those otherwise-unused GPUs instead of
|
||||||
# running it on a single rank. None = replicated, else the group to fold
|
# running it on a single rank. None = replicated, else the group to fold
|
||||||
# over ("sp"|"ulysses"|"ring"|"world"); resolved by finalize_encoder_folding.
|
# over ("sp"|"world"); resolved by finalize_encoder_folding.
|
||||||
parallel_folding_mode: str | None = None
|
parallel_folding_mode: Literal["sp", "world"] | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -595,42 +595,24 @@ def model_parallel_is_initialized() -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_TP_STATE_PATCHED = False
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def patch_tensor_parallel_group(tp_group: GroupCoordinator):
|
def use_tensor_parallel_group(tp_group: GroupCoordinator):
|
||||||
"""Patch the tp group temporarily until this function ends.
|
"""Use one TP group consistently across diffusion and reused SRT modules."""
|
||||||
|
|
||||||
This method is for draft workers of speculative decoding to run draft model
|
|
||||||
with different tp degree from that of target model workers.
|
|
||||||
|
|
||||||
"""
|
|
||||||
global _TP_STATE_PATCHED
|
|
||||||
assert not _TP_STATE_PATCHED, "Should not call when it's already patched"
|
|
||||||
|
|
||||||
_TP_STATE_PATCHED = True
|
|
||||||
old_tp_group = get_tp_group()
|
old_tp_group = get_tp_group()
|
||||||
import sglang.srt.distributed.parallel_state as srt_parallel_state
|
import sglang.srt.distributed.parallel_state as srt_parallel_state
|
||||||
|
|
||||||
patch_srt_tp = srt_parallel_state._TP is old_tp_group
|
old_srt_tp_group = srt_parallel_state._TP
|
||||||
patch_srt_attention_tp = srt_parallel_state._ATTN_TP is old_tp_group
|
old_srt_attention_tp_group = srt_parallel_state._ATTN_TP
|
||||||
global _TP
|
global _TP
|
||||||
_TP = tp_group
|
_TP = tp_group
|
||||||
if patch_srt_tp:
|
|
||||||
srt_parallel_state._TP = tp_group
|
srt_parallel_state._TP = tp_group
|
||||||
if patch_srt_attention_tp:
|
|
||||||
srt_parallel_state._ATTN_TP = tp_group
|
srt_parallel_state._ATTN_TP = tp_group
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
# restore the original state
|
|
||||||
_TP_STATE_PATCHED = False
|
|
||||||
_TP = old_tp_group
|
_TP = old_tp_group
|
||||||
if patch_srt_tp and srt_parallel_state._TP is tp_group:
|
srt_parallel_state._TP = old_srt_tp_group
|
||||||
srt_parallel_state._TP = old_tp_group
|
srt_parallel_state._ATTN_TP = old_srt_attention_tp_group
|
||||||
if patch_srt_attention_tp and srt_parallel_state._ATTN_TP is tp_group:
|
|
||||||
srt_parallel_state._ATTN_TP = old_tp_group
|
|
||||||
|
|
||||||
|
|
||||||
def get_tp_world_size() -> int:
|
def get_tp_world_size() -> int:
|
||||||
|
|||||||
+12
-17
@@ -3,7 +3,6 @@ import glob
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections.abc import Callable, Generator, Iterable
|
from collections.abc import Callable, Generator, Iterable
|
||||||
from contextlib import nullcontext
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -16,11 +15,9 @@ from sglang.multimodal_gen.configs.pipeline_configs.qwen_image import (
|
|||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.distributed import (
|
from sglang.multimodal_gen.runtime.distributed import (
|
||||||
get_local_torch_device,
|
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 (
|
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||||
patch_tensor_parallel_group,
|
use_tensor_parallel_group,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
|
from sglang.multimodal_gen.runtime.loader.component_loaders.component_loader import (
|
||||||
ComponentLoader,
|
ComponentLoader,
|
||||||
@@ -36,6 +33,7 @@ from sglang.multimodal_gen.runtime.loader.weight_utils import (
|
|||||||
safetensors_weights_iterator,
|
safetensors_weights_iterator,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
||||||
|
EncoderTensorParallelMixin,
|
||||||
TextEncoder,
|
TextEncoder,
|
||||||
finalize_encoder_folding,
|
finalize_encoder_folding,
|
||||||
get_folding_tp_group,
|
get_folding_tp_group,
|
||||||
@@ -408,20 +406,10 @@ class TextEncoderLoader(ComponentLoader):
|
|||||||
else:
|
else:
|
||||||
model_device = local_torch_device
|
model_device = local_torch_device
|
||||||
|
|
||||||
# Parallel folding: build + shard the encoder over the folding group (the
|
encoder_tp_group = get_folding_tp_group(model_config)
|
||||||
# idle DiT replica during the encoding stage) instead of the default TP
|
with use_tensor_parallel_group(encoder_tp_group), set_default_torch_dtype(
|
||||||
# group, so every encoder folds without threading the group through each layer.
|
PRECISION_TO_TYPE[dtype]
|
||||||
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():
|
with model_device, skip_init_modules():
|
||||||
architectures = getattr(model_config, "architectures", [])
|
architectures = getattr(model_config, "architectures", [])
|
||||||
model_cls, _ = ModelRegistry.resolve_model_cls(architectures)
|
model_cls, _ = ModelRegistry.resolve_model_cls(architectures)
|
||||||
@@ -435,6 +423,13 @@ class TextEncoderLoader(ComponentLoader):
|
|||||||
model_config.enable_image_understanding = enable_image_understanding
|
model_config.enable_image_understanding = enable_image_understanding
|
||||||
model = model_cls(model_config)
|
model = model_cls(model_config)
|
||||||
|
|
||||||
|
if not isinstance(model, EncoderTensorParallelMixin):
|
||||||
|
raise TypeError(
|
||||||
|
f"Native encoder {model_cls.__name__} must inherit "
|
||||||
|
"EncoderTensorParallelMixin"
|
||||||
|
)
|
||||||
|
model.bind_encoder_tp_group(encoder_tp_group)
|
||||||
|
|
||||||
weights_to_load = {name for name, _ in model.named_parameters()}
|
weights_to_load = {name for name, _ in model.named_parameters()}
|
||||||
loaded_weights = model.load_weights(
|
loaded_weights = model.load_weights(
|
||||||
self._get_all_weights(
|
self._get_all_weights(
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ from sglang.multimodal_gen.runtime.distributed import (
|
|||||||
get_tp_group,
|
get_tp_group,
|
||||||
get_world_group,
|
get_world_group,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.distributed.group_coordinator import GroupCoordinator
|
||||||
|
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||||
|
use_tensor_parallel_group,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||||
LayerwiseOffloadableModuleMixin,
|
LayerwiseOffloadableModuleMixin,
|
||||||
)
|
)
|
||||||
@@ -25,19 +29,16 @@ from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
|||||||
|
|
||||||
|
|
||||||
def get_folding_tp_group(config: EncoderConfig):
|
def get_folding_tp_group(config: EncoderConfig):
|
||||||
"""group an encoder tensor-parallels over; the default TP group unless a
|
"""Return the TP group selected for an encoder."""
|
||||||
fold mode is set"""
|
|
||||||
mode = config.parallel_folding_mode
|
mode = config.parallel_folding_mode
|
||||||
if mode == "sp":
|
if mode == "sp":
|
||||||
return get_sp_group()
|
return get_sp_group()
|
||||||
elif mode == "ulysses":
|
if mode == "world":
|
||||||
return get_sp_group().ulysses_group
|
|
||||||
elif mode == "ring":
|
|
||||||
return get_sp_group().ring_group
|
|
||||||
elif mode == "world":
|
|
||||||
# the whole single-replica DiT (all GPUs), regardless of tp/sp/cfg.
|
# the whole single-replica DiT (all GPUs), regardless of tp/sp/cfg.
|
||||||
return get_world_group()
|
return get_world_group()
|
||||||
|
if mode is None:
|
||||||
return get_tp_group()
|
return get_tp_group()
|
||||||
|
raise ValueError(f"Unsupported encoder folding mode: {mode!r}")
|
||||||
|
|
||||||
|
|
||||||
# measured on 2/4xH100: folding wins only for wide encoders (T5-XXL 4096: -20%
|
# measured on 2/4xH100: folding wins only for wide encoders (T5-XXL 4096: -20%
|
||||||
@@ -148,7 +149,25 @@ def finalize_encoder_folding(
|
|||||||
config.parallel_folding_mode = None
|
config.parallel_folding_mode = None
|
||||||
|
|
||||||
|
|
||||||
class TextEncoder(nn.Module, ABC, LayerwiseOffloadableModuleMixin):
|
class EncoderTensorParallelMixin:
|
||||||
|
"""Keep an encoder on the TP group that was used to build its shards."""
|
||||||
|
|
||||||
|
_encoder_tp_group: GroupCoordinator | None = None
|
||||||
|
|
||||||
|
def bind_encoder_tp_group(self, tp_group: GroupCoordinator) -> None:
|
||||||
|
self._encoder_tp_group = tp_group
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
tp_group = self._encoder_tp_group
|
||||||
|
if tp_group is None:
|
||||||
|
return super().__call__(*args, **kwargs)
|
||||||
|
with use_tensor_parallel_group(tp_group):
|
||||||
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TextEncoder(
|
||||||
|
EncoderTensorParallelMixin, nn.Module, ABC, LayerwiseOffloadableModuleMixin
|
||||||
|
):
|
||||||
# Opt in per encoder to data-parallel batched encoding: the gather rebuilds a
|
# Opt in per encoder to data-parallel batched encoding: the gather rebuilds a
|
||||||
# BaseEncoderOutput, and subclasses are free to return their own output type
|
# BaseEncoderOutput, and subclasses are free to return their own output type
|
||||||
# instead (Qwen2_5_VLForConditionalGeneration returns
|
# instead (Qwen2_5_VLForConditionalGeneration returns
|
||||||
@@ -200,7 +219,9 @@ class TextEncoder(nn.Module, ABC, LayerwiseOffloadableModuleMixin):
|
|||||||
return self._supported_attention_backends
|
return self._supported_attention_backends
|
||||||
|
|
||||||
|
|
||||||
class ImageEncoder(nn.Module, ABC, LayerwiseOffloadableModuleMixin):
|
class ImageEncoder(
|
||||||
|
EncoderTensorParallelMixin, nn.Module, ABC, LayerwiseOffloadableModuleMixin
|
||||||
|
):
|
||||||
layerwise_offload_dit_group_enabled = False
|
layerwise_offload_dit_group_enabled = False
|
||||||
layer_names = [
|
layer_names = [
|
||||||
"layers",
|
"layers",
|
||||||
|
|||||||
@@ -17,406 +17,22 @@ from sglang.multimodal_gen.configs.models.encoders import (
|
|||||||
CLIPTextConfig,
|
CLIPTextConfig,
|
||||||
CLIPVisionConfig,
|
CLIPVisionConfig,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.distributed import divide, get_tp_world_size
|
|
||||||
from sglang.multimodal_gen.runtime.layers.activation import get_act_fn
|
|
||||||
from sglang.multimodal_gen.runtime.layers.attention import LocalAttention
|
|
||||||
from sglang.multimodal_gen.runtime.layers.linear import (
|
|
||||||
ColumnParallelLinear,
|
|
||||||
QKVParallelLinear,
|
|
||||||
RowParallelLinear,
|
|
||||||
)
|
|
||||||
from sglang.multimodal_gen.runtime.layers.quantization import QuantizationConfig
|
from sglang.multimodal_gen.runtime.layers.quantization import QuantizationConfig
|
||||||
|
|
||||||
# TODO: support quantization
|
|
||||||
# from vllm.model_executor.layers.quantization import QuantizationConfig
|
|
||||||
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
|
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
|
||||||
from sglang.multimodal_gen.runtime.models.encoders.base import ImageEncoder, TextEncoder
|
from sglang.multimodal_gen.runtime.models.encoders.base import ImageEncoder, TextEncoder
|
||||||
from sglang.multimodal_gen.runtime.models.encoders.vision import (
|
from sglang.multimodal_gen.runtime.models.encoders.vision import (
|
||||||
resolve_visual_encoder_outputs,
|
resolve_visual_encoder_outputs,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.platforms import (
|
from sglang.srt.models.clip import (
|
||||||
AttentionBackendEnum,
|
CLIPEncoder,
|
||||||
current_platform,
|
CLIPTextEmbeddings,
|
||||||
|
CLIPVisionEmbeddings,
|
||||||
|
prepare_clip_attention_mask,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Adapted from https://github.com/huggingface/transformers/blob/v4.39.0/src/transformers/models/clip/modeling_clip.py#L164 # noqa
|
def _srt_clip_param_name(name: str) -> str:
|
||||||
class CLIPVisionEmbeddings(nn.Module):
|
return name.replace(".out_proj.", ".proj.")
|
||||||
|
|
||||||
def __init__(self, config: CLIPVisionConfig):
|
|
||||||
super().__init__()
|
|
||||||
self.config = config
|
|
||||||
self.embed_dim = config.hidden_size
|
|
||||||
self.image_size = config.image_size
|
|
||||||
self.patch_size = config.patch_size
|
|
||||||
assert self.image_size % self.patch_size == 0
|
|
||||||
|
|
||||||
self.class_embedding = nn.Parameter(torch.randn(self.embed_dim))
|
|
||||||
|
|
||||||
self.patch_embedding = nn.Conv2d(
|
|
||||||
in_channels=config.num_channels,
|
|
||||||
out_channels=self.embed_dim,
|
|
||||||
kernel_size=self.patch_size,
|
|
||||||
stride=self.patch_size,
|
|
||||||
bias=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.num_patches = (self.image_size // self.patch_size) ** 2
|
|
||||||
self.num_positions = self.num_patches + 1
|
|
||||||
self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
|
|
||||||
self.register_buffer(
|
|
||||||
"position_ids",
|
|
||||||
torch.arange(self.num_positions).expand((1, -1)),
|
|
||||||
persistent=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
|
|
||||||
batch_size = pixel_values.shape[0]
|
|
||||||
target_dtype = self.patch_embedding.weight.dtype
|
|
||||||
patch_embeds = self.patch_embedding(
|
|
||||||
pixel_values.to(dtype=target_dtype)
|
|
||||||
) # shape = [*, width, grid, grid]
|
|
||||||
patch_embeds = patch_embeds.flatten(2).transpose(1, 2)
|
|
||||||
|
|
||||||
class_embeds = self.class_embedding.expand(batch_size, 1, -1)
|
|
||||||
embeddings = torch.cat([class_embeds, patch_embeds], dim=1)
|
|
||||||
embeddings = embeddings + self.position_embedding(self.position_ids)
|
|
||||||
|
|
||||||
return embeddings
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPTextEmbeddings(nn.Module):
|
|
||||||
|
|
||||||
def __init__(self, config: CLIPTextConfig):
|
|
||||||
super().__init__()
|
|
||||||
self.config = config
|
|
||||||
embed_dim = config.hidden_size
|
|
||||||
|
|
||||||
self.token_embedding = nn.Embedding(config.vocab_size, embed_dim)
|
|
||||||
self.position_embedding = nn.Embedding(
|
|
||||||
config.max_position_embeddings, embed_dim
|
|
||||||
)
|
|
||||||
|
|
||||||
# position_ids (1, len position emb) is contiguous in memory and exported when serialized
|
|
||||||
self.register_buffer(
|
|
||||||
"position_ids",
|
|
||||||
torch.arange(config.max_position_embeddings).expand((1, -1)),
|
|
||||||
persistent=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(
|
|
||||||
self,
|
|
||||||
input_ids: torch.LongTensor | None = None,
|
|
||||||
position_ids: torch.LongTensor | None = None,
|
|
||||||
inputs_embeds: torch.FloatTensor | None = None,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
if input_ids is not None:
|
|
||||||
seq_length = input_ids.shape[-1]
|
|
||||||
elif inputs_embeds is not None:
|
|
||||||
seq_length = inputs_embeds.shape[-2]
|
|
||||||
else:
|
|
||||||
raise ValueError("Either input_ids or inputs_embeds must be provided.")
|
|
||||||
|
|
||||||
max_position_embedding = self.position_embedding.weight.shape[0]
|
|
||||||
|
|
||||||
if seq_length > max_position_embedding:
|
|
||||||
raise ValueError(
|
|
||||||
f"Sequence length must be less than max_position_embeddings (got `sequence length`: "
|
|
||||||
f"{seq_length} and max_position_embeddings: {max_position_embedding}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if position_ids is None:
|
|
||||||
position_ids = self.position_ids[:, :seq_length]
|
|
||||||
|
|
||||||
if inputs_embeds is None:
|
|
||||||
inputs_embeds = self.token_embedding(input_ids)
|
|
||||||
|
|
||||||
position_embeddings = self.position_embedding(position_ids)
|
|
||||||
embeddings = inputs_embeds + position_embeddings
|
|
||||||
|
|
||||||
return embeddings
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPAttention(nn.Module):
|
|
||||||
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: CLIPVisionConfig | CLIPTextConfig,
|
|
||||||
quant_config: QuantizationConfig | None = None,
|
|
||||||
prefix: str = "",
|
|
||||||
):
|
|
||||||
super().__init__()
|
|
||||||
self.config = config
|
|
||||||
self.embed_dim = config.hidden_size
|
|
||||||
self.num_heads = config.num_attention_heads
|
|
||||||
self.head_dim = self.embed_dim // self.num_heads
|
|
||||||
if self.head_dim * self.num_heads != self.embed_dim:
|
|
||||||
raise ValueError(
|
|
||||||
"embed_dim must be divisible by num_heads "
|
|
||||||
f"(got `embed_dim`: {self.embed_dim} and `num_heads`:"
|
|
||||||
f" {self.num_heads})."
|
|
||||||
)
|
|
||||||
self.scale = self.head_dim**-0.5
|
|
||||||
self.dropout = config.attention_dropout
|
|
||||||
|
|
||||||
self.qkv_proj = QKVParallelLinear(
|
|
||||||
hidden_size=self.embed_dim,
|
|
||||||
head_size=self.head_dim,
|
|
||||||
total_num_heads=self.num_heads,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.qkv_proj",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.out_proj = RowParallelLinear(
|
|
||||||
input_size=self.embed_dim,
|
|
||||||
output_size=self.embed_dim,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.out_proj",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.tp_size = get_tp_world_size()
|
|
||||||
self.num_heads_per_partition = divide(self.num_heads, self.tp_size)
|
|
||||||
|
|
||||||
self.attn = LocalAttention(
|
|
||||||
self.num_heads_per_partition,
|
|
||||||
self.head_dim,
|
|
||||||
self.num_heads_per_partition,
|
|
||||||
softmax_scale=self.scale,
|
|
||||||
causal=True,
|
|
||||||
supported_attention_backends=config._supported_attention_backends,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int):
|
|
||||||
return (
|
|
||||||
tensor.view(bsz, seq_len, self.num_heads, self.head_dim)
|
|
||||||
.transpose(1, 2)
|
|
||||||
.contiguous()
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(
|
|
||||||
self,
|
|
||||||
hidden_states: torch.Tensor,
|
|
||||||
attention_mask: torch.Tensor | None = None,
|
|
||||||
):
|
|
||||||
"""Input shape: Batch x Time x Channel"""
|
|
||||||
|
|
||||||
qkv_states, _ = self.qkv_proj(hidden_states)
|
|
||||||
query_states, key_states, value_states = qkv_states.chunk(3, dim=-1)
|
|
||||||
# use flash_attn_func
|
|
||||||
query_states = query_states.reshape(
|
|
||||||
query_states.shape[0],
|
|
||||||
query_states.shape[1],
|
|
||||||
self.num_heads_per_partition,
|
|
||||||
self.head_dim,
|
|
||||||
)
|
|
||||||
key_states = key_states.reshape(
|
|
||||||
key_states.shape[0],
|
|
||||||
key_states.shape[1],
|
|
||||||
self.num_heads_per_partition,
|
|
||||||
self.head_dim,
|
|
||||||
)
|
|
||||||
value_states = value_states.reshape(
|
|
||||||
value_states.shape[0],
|
|
||||||
value_states.shape[1],
|
|
||||||
self.num_heads_per_partition,
|
|
||||||
self.head_dim,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.attn.backend == AttentionBackendEnum.TORCH_SDPA:
|
|
||||||
query_states = query_states.transpose(1, 2) # [B, H, S, D]
|
|
||||||
key_states = key_states.transpose(1, 2)
|
|
||||||
value_states = value_states.transpose(1, 2)
|
|
||||||
|
|
||||||
if (
|
|
||||||
current_platform.is_rocm()
|
|
||||||
or current_platform.is_musa()
|
|
||||||
or current_platform.is_xpu()
|
|
||||||
):
|
|
||||||
# ROCm: Using both is_causal=True and attn_mask causes NaN.
|
|
||||||
# Use is_causal=True alone (padding mask not needed for CLIP
|
|
||||||
# since pooler_output comes from EOS token before padding).
|
|
||||||
# XXX (MUSA): Torch SDPA on MUSA currently does not support
|
|
||||||
# using both `attn_mask` and `is_causal=True` simultaneously.
|
|
||||||
attn_output = torch.nn.functional.scaled_dot_product_attention(
|
|
||||||
query_states,
|
|
||||||
key_states,
|
|
||||||
value_states,
|
|
||||||
attn_mask=None,
|
|
||||||
is_causal=True,
|
|
||||||
scale=self.scale,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if attention_mask is not None:
|
|
||||||
# SDPA requires [B, 1, 1, S] or [B, S, S] format mask
|
|
||||||
if attention_mask.dim() == 2:
|
|
||||||
attn_mask = attention_mask[:, None, None, :].to(
|
|
||||||
dtype=query_states.dtype
|
|
||||||
)
|
|
||||||
attn_mask = (1.0 - attn_mask) * torch.finfo(
|
|
||||||
query_states.dtype
|
|
||||||
).min
|
|
||||||
else:
|
|
||||||
attn_mask = attention_mask
|
|
||||||
else:
|
|
||||||
attn_mask = None
|
|
||||||
|
|
||||||
attn_output = torch.nn.functional.scaled_dot_product_attention(
|
|
||||||
query_states,
|
|
||||||
key_states,
|
|
||||||
value_states,
|
|
||||||
attn_mask=attn_mask,
|
|
||||||
is_causal=attention_mask is None,
|
|
||||||
scale=self.scale,
|
|
||||||
)
|
|
||||||
attn_output = attn_output.transpose(1, 2)
|
|
||||||
else:
|
|
||||||
# Use LocalAttention (doesn't support attention_mask, but maintains compatibility)
|
|
||||||
attn_output = self.attn(query_states, key_states, value_states)
|
|
||||||
|
|
||||||
attn_output = attn_output.reshape(
|
|
||||||
attn_output.shape[0],
|
|
||||||
attn_output.shape[1],
|
|
||||||
self.num_heads_per_partition * self.head_dim,
|
|
||||||
)
|
|
||||||
attn_output, _ = self.out_proj(attn_output)
|
|
||||||
|
|
||||||
return attn_output, None
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPMLP(nn.Module):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: CLIPVisionConfig | CLIPTextConfig,
|
|
||||||
quant_config: QuantizationConfig | None = None,
|
|
||||||
prefix: str = "",
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.config = config
|
|
||||||
self.activation_fn = get_act_fn(config.hidden_act)
|
|
||||||
self.fc1 = ColumnParallelLinear(
|
|
||||||
config.hidden_size,
|
|
||||||
config.intermediate_size,
|
|
||||||
bias=True,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.fc1",
|
|
||||||
)
|
|
||||||
self.fc2 = RowParallelLinear(
|
|
||||||
config.intermediate_size,
|
|
||||||
config.hidden_size,
|
|
||||||
bias=True,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.fc2",
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
||||||
hidden_states, _ = self.fc1(hidden_states)
|
|
||||||
hidden_states = self.activation_fn(hidden_states)
|
|
||||||
hidden_states, _ = self.fc2(hidden_states)
|
|
||||||
|
|
||||||
return hidden_states
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPEncoderLayer(nn.Module):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: CLIPTextConfig | CLIPVisionConfig,
|
|
||||||
quant_config: QuantizationConfig | None = None,
|
|
||||||
prefix: str = "",
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.self_attn = CLIPAttention(
|
|
||||||
config,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.self_attn",
|
|
||||||
)
|
|
||||||
self.layer_norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
|
||||||
self.mlp = CLIPMLP(config, quant_config=quant_config, prefix=f"{prefix}.mlp")
|
|
||||||
self.layer_norm2 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
|
||||||
|
|
||||||
def forward(
|
|
||||||
self,
|
|
||||||
hidden_states: torch.Tensor,
|
|
||||||
attention_mask: torch.Tensor | None = None,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
residual = hidden_states
|
|
||||||
|
|
||||||
hidden_states = self.layer_norm1(hidden_states)
|
|
||||||
hidden_states, _ = self.self_attn(
|
|
||||||
hidden_states=hidden_states,
|
|
||||||
attention_mask=attention_mask,
|
|
||||||
)
|
|
||||||
hidden_states = residual + hidden_states
|
|
||||||
|
|
||||||
residual = hidden_states
|
|
||||||
hidden_states = self.layer_norm2(hidden_states)
|
|
||||||
hidden_states = self.mlp(hidden_states)
|
|
||||||
hidden_states = residual + hidden_states
|
|
||||||
|
|
||||||
return hidden_states
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPEncoder(nn.Module):
|
|
||||||
"""
|
|
||||||
Transformer encoder consisting of `config.num_hidden_layers` self
|
|
||||||
attention layers. Each layer is a [`CLIPEncoderLayer`].
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: CLIPConfig
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: CLIPVisionConfig | CLIPTextConfig,
|
|
||||||
quant_config: QuantizationConfig | None = None,
|
|
||||||
num_hidden_layers_override: int | None = None,
|
|
||||||
prefix: str = "",
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
if num_hidden_layers_override is None:
|
|
||||||
num_hidden_layers = config.num_hidden_layers
|
|
||||||
else:
|
|
||||||
num_hidden_layers = num_hidden_layers_override
|
|
||||||
self.layers = nn.ModuleList(
|
|
||||||
[
|
|
||||||
CLIPEncoderLayer(
|
|
||||||
config=config,
|
|
||||||
quant_config=quant_config,
|
|
||||||
prefix=f"{prefix}.layers.{layer_idx}",
|
|
||||||
)
|
|
||||||
for layer_idx in range(num_hidden_layers)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(
|
|
||||||
self,
|
|
||||||
inputs_embeds: torch.Tensor,
|
|
||||||
return_all_hidden_states: bool,
|
|
||||||
attention_mask: torch.Tensor | None = None,
|
|
||||||
) -> torch.Tensor | list[torch.Tensor]:
|
|
||||||
hidden_states_pool = [inputs_embeds]
|
|
||||||
hidden_states = inputs_embeds
|
|
||||||
|
|
||||||
for idx, encoder_layer in enumerate(self.layers):
|
|
||||||
hidden_states = encoder_layer(
|
|
||||||
hidden_states,
|
|
||||||
attention_mask=attention_mask,
|
|
||||||
)
|
|
||||||
if return_all_hidden_states:
|
|
||||||
hidden_states_pool.append(hidden_states)
|
|
||||||
# If we have multiple feature sample layers, we return all hidden
|
|
||||||
# states in order and grab the ones we need by index.
|
|
||||||
if return_all_hidden_states:
|
|
||||||
return hidden_states_pool
|
|
||||||
return [hidden_states]
|
|
||||||
|
|
||||||
|
|
||||||
class CLIPTextTransformer(nn.Module):
|
class CLIPTextTransformer(nn.Module):
|
||||||
@@ -439,6 +55,7 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
num_hidden_layers_override=num_hidden_layers_override,
|
num_hidden_layers_override=num_hidden_layers_override,
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
|
causal=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
||||||
@@ -468,17 +85,12 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
|
|
||||||
hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids)
|
hidden_states = self.embeddings(input_ids=input_ids, position_ids=position_ids)
|
||||||
|
|
||||||
# CLIP's text model uses causal mask, prepare it here.
|
attention_mask = prepare_clip_attention_mask(
|
||||||
# https://github.com/openai/CLIP/blob/cfcffb90e69f37bf2ff1e988237a0fbe41f33c04/clip/model.py#L324
|
input_shape,
|
||||||
# causal_attention_mask = _create_4d_causal_attention_mask(
|
hidden_states.dtype,
|
||||||
# input_shape, hidden_states.dtype, device=hidden_states.device
|
hidden_states.device,
|
||||||
# )
|
attention_mask,
|
||||||
|
)
|
||||||
# # expand attention_mask
|
|
||||||
# if attention_mask is not None and not self._use_flash_attention_2:
|
|
||||||
# raise NotImplementedError("attention_mask is not supported for CLIPTextTransformer")
|
|
||||||
# # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
|
|
||||||
# attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype)
|
|
||||||
|
|
||||||
encoder_outputs = self.encoder(
|
encoder_outputs = self.encoder(
|
||||||
inputs_embeds=hidden_states,
|
inputs_embeds=hidden_states,
|
||||||
@@ -486,7 +98,12 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
attention_mask=attention_mask,
|
attention_mask=attention_mask,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if output_hidden_states:
|
||||||
|
all_hidden_states = encoder_outputs
|
||||||
last_hidden_state = encoder_outputs[-1]
|
last_hidden_state = encoder_outputs[-1]
|
||||||
|
else:
|
||||||
|
last_hidden_state = encoder_outputs
|
||||||
|
all_hidden_states = [encoder_outputs]
|
||||||
last_hidden_state = self.final_layer_norm(last_hidden_state)
|
last_hidden_state = self.final_layer_norm(last_hidden_state)
|
||||||
|
|
||||||
if self.eos_token_id == 2:
|
if self.eos_token_id == 2:
|
||||||
@@ -523,8 +140,7 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
return BaseEncoderOutput(
|
return BaseEncoderOutput(
|
||||||
last_hidden_state=last_hidden_state,
|
last_hidden_state=last_hidden_state,
|
||||||
pooler_output=pooled_output,
|
pooler_output=pooled_output,
|
||||||
hidden_states=encoder_outputs,
|
hidden_states=all_hidden_states,
|
||||||
# attentions=encoder_outputs.attentions,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -569,6 +185,7 @@ class CLIPTextModel(TextEncoder):
|
|||||||
params_dict = dict(self.named_parameters())
|
params_dict = dict(self.named_parameters())
|
||||||
loaded_params: set[str] = set()
|
loaded_params: set[str] = set()
|
||||||
for name, loaded_weight in weights:
|
for name, loaded_weight in weights:
|
||||||
|
name = _srt_clip_param_name(name)
|
||||||
# Handle q_proj, k_proj, v_proj -> qkv_proj mapping
|
# Handle q_proj, k_proj, v_proj -> qkv_proj mapping
|
||||||
for param_name, weight_name, shard_id in stacked_params_mapping:
|
for param_name, weight_name, shard_id in stacked_params_mapping:
|
||||||
if weight_name in name:
|
if weight_name in name:
|
||||||
@@ -702,8 +319,6 @@ class CLIPVisionTransformer(nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not return_all_hidden_states:
|
if not return_all_hidden_states:
|
||||||
encoder_outputs = encoder_outputs[0]
|
|
||||||
|
|
||||||
# Handle post-norm (if applicable) and stacks feature layers if needed
|
# Handle post-norm (if applicable) and stacks feature layers if needed
|
||||||
encoder_outputs = resolve_visual_encoder_outputs(
|
encoder_outputs = resolve_visual_encoder_outputs(
|
||||||
encoder_outputs,
|
encoder_outputs,
|
||||||
@@ -763,6 +378,7 @@ class CLIPVisionModel(ImageEncoder):
|
|||||||
for name, loaded_weight in weights:
|
for name, loaded_weight in weights:
|
||||||
if name.startswith("visual_projection"):
|
if name.startswith("visual_projection"):
|
||||||
continue
|
continue
|
||||||
|
name = _srt_clip_param_name(name)
|
||||||
# post_layernorm is not needed in CLIPVisionModel
|
# post_layernorm is not needed in CLIPVisionModel
|
||||||
if (
|
if (
|
||||||
name.startswith("vision_model.post_layernorm")
|
name.startswith("vision_model.post_layernorm")
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loa
|
|||||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||||
LayerwiseOffloadableModuleMixin,
|
LayerwiseOffloadableModuleMixin,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
||||||
|
EncoderTensorParallelMixin,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -283,7 +286,9 @@ class Gemma2DecoderLayer(nn.Module):
|
|||||||
return hidden_states
|
return hidden_states
|
||||||
|
|
||||||
|
|
||||||
class Gemma2Model(nn.Module, LayerwiseOffloadableModuleMixin):
|
class Gemma2Model(
|
||||||
|
EncoderTensorParallelMixin, nn.Module, LayerwiseOffloadableModuleMixin
|
||||||
|
):
|
||||||
"""Gemma2 text encoder model for SANA pipeline."""
|
"""Gemma2 text encoder model for SANA pipeline."""
|
||||||
|
|
||||||
_fsdp_shard_conditions = []
|
_fsdp_shard_conditions = []
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
# Adapted from sglang: python/sglang/srt/models/gemma3_causal.py
|
# Adapted from sglang: python/sglang/srt/models/gemma3_causal.py
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from contextlib import nullcontext
|
|
||||||
from typing import Any, Iterable, Optional, Set, Tuple
|
from typing import Any, Iterable, Optional, Set, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -12,10 +11,7 @@ from torch import nn
|
|||||||
|
|
||||||
from sglang.multimodal_gen.configs.models.encoders.base import BaseEncoderOutput
|
from sglang.multimodal_gen.configs.models.encoders.base import BaseEncoderOutput
|
||||||
from sglang.multimodal_gen.configs.models.encoders.gemma_3 import Gemma3Config
|
from sglang.multimodal_gen.configs.models.encoders.gemma_3 import Gemma3Config
|
||||||
from sglang.multimodal_gen.runtime.distributed import get_tp_group, get_tp_world_size
|
from sglang.multimodal_gen.runtime.distributed import get_tp_world_size
|
||||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
|
||||||
patch_tensor_parallel_group,
|
|
||||||
)
|
|
||||||
from sglang.multimodal_gen.runtime.layers.activation import GeluAndMul
|
from sglang.multimodal_gen.runtime.layers.activation import GeluAndMul
|
||||||
from sglang.multimodal_gen.runtime.layers.linear import (
|
from sglang.multimodal_gen.runtime.layers.linear import (
|
||||||
MergedColumnParallelLinear,
|
MergedColumnParallelLinear,
|
||||||
@@ -28,6 +24,9 @@ from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loa
|
|||||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||||
LayerwiseOffloadableModuleMixin,
|
LayerwiseOffloadableModuleMixin,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
||||||
|
EncoderTensorParallelMixin,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.utils.common import add_prefix
|
from sglang.multimodal_gen.runtime.utils.common import add_prefix
|
||||||
from sglang.srt.models.siglip import SiglipVisionModel
|
from sglang.srt.models.siglip import SiglipVisionModel
|
||||||
|
|
||||||
@@ -678,7 +677,9 @@ class Gemma3TextModel(nn.Module):
|
|||||||
return loaded_params
|
return loaded_params
|
||||||
|
|
||||||
|
|
||||||
class Gemma3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin):
|
class Gemma3ForConditionalGeneration(
|
||||||
|
EncoderTensorParallelMixin, nn.Module, LayerwiseOffloadableModuleMixin
|
||||||
|
):
|
||||||
# transformers 5.6.0 flattened SiglipVisionModel, dropping the
|
# transformers 5.6.0 flattened SiglipVisionModel, dropping the
|
||||||
# `vision_model` intermediate wrapper. Our reimpl keeps it, so remap
|
# `vision_model` intermediate wrapper. Our reimpl keeps it, so remap
|
||||||
# HF source keys back into our nested namespace when transferring weights.
|
# HF source keys back into our nested namespace when transferring weights.
|
||||||
@@ -704,7 +705,6 @@ class Gemma3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin)
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.quant_config = quant_config
|
self.quant_config = quant_config
|
||||||
self.text_config = config.text_config
|
self.text_config = config.text_config
|
||||||
self._vision_tensor_parallel_group = get_tp_group()
|
|
||||||
|
|
||||||
# Vision Tower
|
# Vision Tower
|
||||||
self.vision_tower = SiglipVisionModel(
|
self.vision_tower = SiglipVisionModel(
|
||||||
@@ -720,11 +720,6 @@ class Gemma3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin)
|
|||||||
# Text Model
|
# Text Model
|
||||||
self.language_model = Gemma3TextModel(config)
|
self.language_model = Gemma3TextModel(config)
|
||||||
|
|
||||||
def _vision_parallel_context(self):
|
|
||||||
if get_tp_group() is self._vision_tensor_parallel_group:
|
|
||||||
return nullcontext()
|
|
||||||
return patch_tensor_parallel_group(self._vision_tensor_parallel_group)
|
|
||||||
|
|
||||||
def get_placeholder_mask(
|
def get_placeholder_mask(
|
||||||
self,
|
self,
|
||||||
input_ids: torch.LongTensor,
|
input_ids: torch.LongTensor,
|
||||||
@@ -777,7 +772,6 @@ class Gemma3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin)
|
|||||||
elif pixel_values.dim() != 4:
|
elif pixel_values.dim() != 4:
|
||||||
raise ValueError(f"Unexpected pixel_values shape: {pixel_values.shape}")
|
raise ValueError(f"Unexpected pixel_values shape: {pixel_values.shape}")
|
||||||
|
|
||||||
with self._vision_parallel_context():
|
|
||||||
vision_outputs = self.vision_tower(pixel_values)
|
vision_outputs = self.vision_tower(pixel_values)
|
||||||
image_features = self.multi_modal_projector(vision_outputs)
|
image_features = self.multi_modal_projector(vision_outputs)
|
||||||
image_features = image_features.to(
|
image_features = image_features.to(
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class IdeogramQwen3VLTextEncoder(TextEncoder):
|
|||||||
position_ids = pos_2d[None, ...].expand(4, 1, -1)
|
position_ids = pos_2d[None, ...].expand(4, 1, -1)
|
||||||
attention_mask = torch.ones_like(cur_token_ids)
|
attention_mask = torch.ones_like(cur_token_ids)
|
||||||
with set_forward_context(current_timestep=0, attn_metadata=None):
|
with set_forward_context(current_timestep=0, attn_metadata=None):
|
||||||
outputs = self.forward(
|
outputs = self(
|
||||||
input_ids=cur_token_ids,
|
input_ids=cur_token_ids,
|
||||||
position_ids=position_ids,
|
position_ids=position_ids,
|
||||||
attention_mask=attention_mask,
|
attention_mask=attention_mask,
|
||||||
|
|||||||
@@ -41,9 +41,6 @@ class MiniMaxH3Qwen3VLEncoder(TextEncoder):
|
|||||||
eight otherwise-idle ranks during encoding.
|
eight otherwise-idle ranks during encoding.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# encode_ids drives the forward pass; __call__ is never used, so FSDP2
|
|
||||||
# needs it registered or the root group (the vision tower) stays sharded.
|
|
||||||
_fsdp_forward_methods = ("encode_ids",)
|
|
||||||
layer_names = [*TextEncoder.layer_names, "model.visual.blocks"]
|
layer_names = [*TextEncoder.layer_names, "model.visual.blocks"]
|
||||||
|
|
||||||
supports_dp_encode = True
|
supports_dp_encode = True
|
||||||
@@ -150,10 +147,6 @@ class MiniMaxH3Qwen3VLEncoder(TextEncoder):
|
|||||||
call_kwargs: dict[str, Any] = {
|
call_kwargs: dict[str, Any] = {
|
||||||
"input_ids": ids,
|
"input_ids": ids,
|
||||||
"attention_mask": torch.ones_like(ids),
|
"attention_mask": torch.ones_like(ids),
|
||||||
"output_attentions": False,
|
|
||||||
"output_hidden_states": False,
|
|
||||||
"return_dict": True,
|
|
||||||
"use_cache": False,
|
|
||||||
}
|
}
|
||||||
if position_ids is not None:
|
if position_ids is not None:
|
||||||
call_kwargs["position_ids"] = position_ids.to(self.device)
|
call_kwargs["position_ids"] = position_ids.to(self.device)
|
||||||
@@ -166,7 +159,7 @@ class MiniMaxH3Qwen3VLEncoder(TextEncoder):
|
|||||||
)
|
)
|
||||||
call_kwargs["video_grid_thw"] = host_video_grid_thw
|
call_kwargs["video_grid_thw"] = host_video_grid_thw
|
||||||
|
|
||||||
hidden = self.model(**call_kwargs).last_hidden_state[0].to(torch.bfloat16)
|
hidden = self(**call_kwargs).last_hidden_state[0].to(torch.bfloat16)
|
||||||
expected_shape = [int(ids.shape[1]), self.hidden_dim]
|
expected_shape = [int(ids.shape[1]), self.hidden_dim]
|
||||||
if list(hidden.shape) != expected_shape:
|
if list(hidden.shape) != expected_shape:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loa
|
|||||||
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
||||||
LayerwiseOffloadableModuleMixin,
|
LayerwiseOffloadableModuleMixin,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
||||||
|
EncoderTensorParallelMixin,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.platforms import (
|
from sglang.multimodal_gen.runtime.platforms import (
|
||||||
AttentionBackendEnum,
|
AttentionBackendEnum,
|
||||||
current_platform,
|
current_platform,
|
||||||
@@ -635,7 +638,9 @@ class Mistral3Model(nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Mistral3ForConditionalGeneration(nn.Module, LayerwiseOffloadableModuleMixin):
|
class Mistral3ForConditionalGeneration(
|
||||||
|
EncoderTensorParallelMixin, nn.Module, LayerwiseOffloadableModuleMixin
|
||||||
|
):
|
||||||
_checkpoint_conversion_mapping = {
|
_checkpoint_conversion_mapping = {
|
||||||
"^language_model.model": "model.language_model",
|
"^language_model.model": "model.language_model",
|
||||||
"^multi_modal_projector": "model.multi_modal_projector",
|
"^multi_modal_projector": "model.multi_modal_projector",
|
||||||
|
|||||||
@@ -232,22 +232,22 @@ def _resolve_warmup_num_frames(
|
|||||||
server_based_warmup: bool,
|
server_based_warmup: bool,
|
||||||
) -> int:
|
) -> int:
|
||||||
num_frames = getattr(sampling_defaults, "num_frames", 1)
|
num_frames = getattr(sampling_defaults, "num_frames", 1)
|
||||||
if (
|
if not _is_video_warmup_task(server_args) or num_frames is None:
|
||||||
not server_based_warmup
|
|
||||||
or not _is_video_warmup_task(server_args)
|
|
||||||
or num_frames is None
|
|
||||||
):
|
|
||||||
# use default num frames
|
|
||||||
return num_frames
|
return num_frames
|
||||||
|
|
||||||
# Breakable CUDA graph replays only exact latent shapes: the warmup
|
# Breakable CUDA graph replays only exact latent shapes: the warmup
|
||||||
# request must run the full serving frame count so its captured graphs
|
# request must run the full serving frame count so its captured graphs
|
||||||
# match serving signatures (mirrors the uncapped-steps rule in
|
# match serving signatures (mirrors the uncapped-steps rule in
|
||||||
# _resolve_warmup_steps).
|
# _resolve_warmup_steps).
|
||||||
if getattr(server_args, "enable_breakable_cuda_graph", False) is True:
|
if (
|
||||||
return num_frames
|
not server_based_warmup
|
||||||
|
or getattr(server_args, "enable_breakable_cuda_graph", False) is True
|
||||||
|
):
|
||||||
|
warmup_num_frames = num_frames
|
||||||
|
else:
|
||||||
|
warmup_num_frames = min(num_frames, SERVER_WARMUP_MAX_VIDEO_FRAMES)
|
||||||
|
|
||||||
return min(num_frames, SERVER_WARMUP_MAX_VIDEO_FRAMES)
|
return server_args.pipeline_config.adjust_num_frames(warmup_num_frames)
|
||||||
|
|
||||||
|
|
||||||
def _effective_cfg_scale(sampling_defaults: SamplingParams) -> float | None:
|
def _effective_cfg_scale(sampling_defaults: SamplingParams) -> float | None:
|
||||||
|
|||||||
@@ -1177,6 +1177,7 @@ STANDALONE_FILES = {
|
|||||||
"../single_test_file/test_disagg_server.py",
|
"../single_test_file/test_disagg_server.py",
|
||||||
"../single_test_file/test_ar_models.py",
|
"../single_test_file/test_ar_models.py",
|
||||||
"../single_test_file/test_ipc_a2a_2_gpu.py",
|
"../single_test_file/test_ipc_a2a_2_gpu.py",
|
||||||
|
"../single_test_file/test_encoder_fold_srt_2_gpu.py",
|
||||||
"../single_test_file/test_diffusion_bcg_tp2_zimage_turbo.py",
|
"../single_test_file/test_diffusion_bcg_tp2_zimage_turbo.py",
|
||||||
"../single_test_file/test_dp_serving_2_gpu.py",
|
"../single_test_file/test_dp_serving_2_gpu.py",
|
||||||
"../single_test_file/test_pynccl_a2a_capture_2_gpu.py",
|
"../single_test_file/test_pynccl_a2a_capture_2_gpu.py",
|
||||||
@@ -1215,6 +1216,7 @@ STANDALONE_FILE_EST_TIMES = {
|
|||||||
"../single_test_file/test_ar_models.py": 600.0,
|
"../single_test_file/test_ar_models.py": 600.0,
|
||||||
# no model load; the cost is the one-time JIT build of the sync kernels
|
# no model load; the cost is the one-time JIT build of the sync kernels
|
||||||
"../single_test_file/test_ipc_a2a_2_gpu.py": 240.0,
|
"../single_test_file/test_ipc_a2a_2_gpu.py": 240.0,
|
||||||
|
"../single_test_file/test_encoder_fold_srt_2_gpu.py": 240.0,
|
||||||
# ~60 s locally with a warm HF cache (load + one capture + 4 steps);
|
# ~60 s locally with a warm HF cache (load + one capture + 4 steps);
|
||||||
# padded for cold-cache CI.
|
# padded for cold-cache CI.
|
||||||
"../single_test_file/test_diffusion_bcg_tp2_zimage_turbo.py": 180.0,
|
"../single_test_file/test_diffusion_bcg_tp2_zimage_turbo.py": 180.0,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import time
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Sequence
|
from typing import Any, Callable, Sequence
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -404,6 +405,9 @@ class ServerManager:
|
|||||||
]
|
]
|
||||||
if self.extra_args.strip():
|
if self.extra_args.strip():
|
||||||
command.extend(self.extra_args.strip().split())
|
command.extend(self.extra_args.strip().split())
|
||||||
|
access_log_exclude_flag = "--uvicorn-access-log-exclude-prefixes"
|
||||||
|
if not any(arg.startswith(access_log_exclude_flag) for arg in command):
|
||||||
|
command.extend(["--uvicorn-access-log-exclude-prefixes", "/health"])
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["SGLANG_DIFFUSION_STAGE_LOGGING"] = "1"
|
env["SGLANG_DIFFUSION_STAGE_LOGGING"] = "1"
|
||||||
@@ -471,9 +475,9 @@ class ServerManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _wait_for_ready(self, process: subprocess.Popen, stdout_path: Path) -> None:
|
def _wait_for_ready(self, process: subprocess.Popen, stdout_path: Path) -> None:
|
||||||
"""Wait for server to become ready."""
|
"""Wait until model warmup finishes and inference traffic is accepted."""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
ready_message = "Application startup complete."
|
health_url = f"http://127.0.0.1:{self.port}/health"
|
||||||
log_period = 30
|
log_period = 30
|
||||||
prev_log_period_count = 0
|
prev_log_period_count = 0
|
||||||
|
|
||||||
@@ -484,14 +488,13 @@ class ServerManager:
|
|||||||
f"Server exited early (code {process.returncode}).\n{tail}"
|
f"Server exited early (code {process.returncode}).\n{tail}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdout_path.exists():
|
|
||||||
try:
|
try:
|
||||||
content = stdout_path.read_text(encoding="utf-8", errors="ignore")
|
with urlopen(health_url, timeout=1) as response:
|
||||||
if ready_message in content:
|
if response.status == 200:
|
||||||
logger.info("[server-test] Server ready")
|
logger.info("[server-test] Server ready")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except (HTTPError, URLError, TimeoutError, OSError):
|
||||||
logger.debug("Could not read log yet: %s", e)
|
pass
|
||||||
|
|
||||||
elapsed = int(time.time() - start)
|
elapsed = int(time.time() - start)
|
||||||
if (elapsed // log_period) > prev_log_period_count:
|
if (elapsed // log_period) > prev_log_period_count:
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
"""Two-rank encoder folding must preserve single-rank native output.
|
||||||
|
|
||||||
|
The focused CLIP check isolates the component loader and SRT tensor-parallel
|
||||||
|
layers. The tiny SD3 check covers the public server API and complete pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||||
|
from sglang.test.test_utils import CustomTestCase
|
||||||
|
|
||||||
|
_WORLD = 2
|
||||||
|
_TINY_SD3_MODEL = "yujiepan/stable-diffusion-3-tiny-random"
|
||||||
|
_TINY_SD3_REVISION = "abcdbb999b2d30c35d03efdce0be981e1efac0a4"
|
||||||
|
|
||||||
|
|
||||||
|
def _tiny_clip_config():
|
||||||
|
from sglang.multimodal_gen.configs.models.encoders.clip import (
|
||||||
|
CLIPTextArchConfig,
|
||||||
|
CLIPTextConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CLIPTextConfig(
|
||||||
|
arch_config=CLIPTextArchConfig(
|
||||||
|
architectures=["CLIPTextModel"],
|
||||||
|
vocab_size=32,
|
||||||
|
hidden_size=8,
|
||||||
|
intermediate_size=16,
|
||||||
|
projection_dim=8,
|
||||||
|
num_hidden_layers=1,
|
||||||
|
num_attention_heads=2,
|
||||||
|
max_position_embeddings=8,
|
||||||
|
pad_token_id=0,
|
||||||
|
bos_token_id=1,
|
||||||
|
eos_token_id=2,
|
||||||
|
text_len=8,
|
||||||
|
),
|
||||||
|
prefix="clip",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _deterministic_state_dict(model: torch.nn.Module) -> dict[str, torch.Tensor]:
|
||||||
|
generator = torch.Generator(device="cpu").manual_seed(20260816)
|
||||||
|
state_dict = {}
|
||||||
|
for name, value in model.state_dict().items():
|
||||||
|
state_dict[name] = torch.randn(
|
||||||
|
value.shape,
|
||||||
|
dtype=value.dtype,
|
||||||
|
generator=generator,
|
||||||
|
).mul_(0.02)
|
||||||
|
return state_dict
|
||||||
|
|
||||||
|
|
||||||
|
def _clip_checkpoint_weights(
|
||||||
|
state_dict: dict[str, torch.Tensor],
|
||||||
|
) -> list[tuple[str, torch.Tensor]]:
|
||||||
|
weights = []
|
||||||
|
for name, value in state_dict.items():
|
||||||
|
if ".qkv_proj." not in name:
|
||||||
|
weights.append((name, value))
|
||||||
|
continue
|
||||||
|
for projection, shard in zip(("q", "k", "v"), value.chunk(3, dim=0)):
|
||||||
|
weights.append((name.replace("qkv_proj", f"{projection}_proj"), shard))
|
||||||
|
return weights
|
||||||
|
|
||||||
|
|
||||||
|
def _worker() -> int:
|
||||||
|
from sglang.multimodal_gen.runtime.distributed import (
|
||||||
|
cleanup_dist_env_and_memory,
|
||||||
|
get_tp_group,
|
||||||
|
get_world_group,
|
||||||
|
init_distributed_environment,
|
||||||
|
initialize_model_parallel,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.loader.component_loaders.text_encoder_loader import (
|
||||||
|
TextEncoderLoader,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders.clip import CLIPTextModel
|
||||||
|
from sglang.srt.distributed import parallel_state as srt_parallel_state
|
||||||
|
|
||||||
|
rank = int(os.environ["RANK"])
|
||||||
|
world_size = int(os.environ["WORLD_SIZE"])
|
||||||
|
device = torch.device(f"cuda:{rank}")
|
||||||
|
torch.cuda.set_device(device)
|
||||||
|
init_distributed_environment(
|
||||||
|
world_size=world_size,
|
||||||
|
rank=rank,
|
||||||
|
local_rank=rank,
|
||||||
|
)
|
||||||
|
initialize_model_parallel(
|
||||||
|
tensor_parallel_degree=1,
|
||||||
|
sequence_parallel_degree=world_size,
|
||||||
|
ulysses_degree=world_size,
|
||||||
|
ring_degree=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
config = _tiny_clip_config()
|
||||||
|
reference = CLIPTextModel(config).to(device).eval()
|
||||||
|
state_dict = _deterministic_state_dict(reference)
|
||||||
|
reference.load_state_dict(
|
||||||
|
{name: value.to(device) for name, value in state_dict.items()}
|
||||||
|
)
|
||||||
|
|
||||||
|
class InMemoryTextEncoderLoader(TextEncoderLoader):
|
||||||
|
def _get_all_weights(self, model, model_path, to_cpu):
|
||||||
|
del model, model_path, to_cpu
|
||||||
|
yield from _clip_checkpoint_weights(state_dict)
|
||||||
|
|
||||||
|
config.parallel_folding_mode = "world"
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(),
|
||||||
|
should_start_component_on_cpu=lambda component_name: False,
|
||||||
|
)
|
||||||
|
folded = InMemoryTextEncoderLoader().load_model(
|
||||||
|
"unused",
|
||||||
|
config,
|
||||||
|
server_args,
|
||||||
|
dtype="fp32",
|
||||||
|
component_starts_on_cpu=False,
|
||||||
|
)
|
||||||
|
folded.eval()
|
||||||
|
|
||||||
|
fold_group = get_world_group()
|
||||||
|
assert folded._encoder_tp_group is fold_group
|
||||||
|
assert folded.text_model.encoder.layers[0].mlp.fc2.tp_size == world_size
|
||||||
|
assert get_tp_group().world_size == 1
|
||||||
|
|
||||||
|
input_ids = torch.tensor([[1, 7, 11, 2]], device=device)
|
||||||
|
with torch.no_grad():
|
||||||
|
expected = reference(input_ids=input_ids).last_hidden_state
|
||||||
|
actual = folded(input_ids=input_ids).last_hidden_state
|
||||||
|
|
||||||
|
torch.testing.assert_close(actual, expected, rtol=2e-5, atol=2e-5)
|
||||||
|
assert get_tp_group().world_size == 1
|
||||||
|
assert srt_parallel_state.get_tp_group().world_size == 1
|
||||||
|
assert srt_parallel_state.get_attn_tp_group().world_size == 1
|
||||||
|
|
||||||
|
if rank == 0:
|
||||||
|
print("ENCODER_FOLD_SRT_PARITY PASS", flush=True)
|
||||||
|
torch.distributed.barrier()
|
||||||
|
cleanup_dist_env_and_memory()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_tiny_sd3(*, fold: bool):
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.test.server.test_server_utils import (
|
||||||
|
ServerManager,
|
||||||
|
get_generate_fn,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.test.server.testcase_configs import (
|
||||||
|
DiffusionSamplingParams,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.test.test_utils import (
|
||||||
|
find_free_port,
|
||||||
|
image_bytes_to_numpy,
|
||||||
|
)
|
||||||
|
|
||||||
|
sampling_params = DiffusionSamplingParams(
|
||||||
|
prompt="a red cube",
|
||||||
|
output_size="64x64",
|
||||||
|
extras={"num_inference_steps": 2, "seed": 0, "guidance_scale": 1.0},
|
||||||
|
)
|
||||||
|
encoder_mode = "fold" if fold else "replicate"
|
||||||
|
parallel_args = f"--num-gpus 2 --ulysses-degree 2 --encoder-parallel {encoder_mode}"
|
||||||
|
extra_args = " ".join(
|
||||||
|
[
|
||||||
|
"--model-type diffusion",
|
||||||
|
"--backend sglang",
|
||||||
|
"--model-id stable-diffusion-3-medium",
|
||||||
|
f"--served-model-name {_TINY_SD3_MODEL}",
|
||||||
|
f"--revision {_TINY_SD3_REVISION}",
|
||||||
|
"--strict-ports",
|
||||||
|
parallel_args,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
manager = ServerManager(
|
||||||
|
model=_TINY_SD3_MODEL,
|
||||||
|
port=find_free_port(),
|
||||||
|
wait_deadline=600,
|
||||||
|
extra_args=extra_args,
|
||||||
|
)
|
||||||
|
ctx = manager.start()
|
||||||
|
try:
|
||||||
|
client = OpenAI(
|
||||||
|
api_key="sglang-anything",
|
||||||
|
base_url=f"http://localhost:{ctx.port}/v1",
|
||||||
|
timeout=600,
|
||||||
|
max_retries=0,
|
||||||
|
)
|
||||||
|
model_ids = [model.id for model in client.models.list().data]
|
||||||
|
assert _TINY_SD3_MODEL in model_ids
|
||||||
|
|
||||||
|
generate = get_generate_fn(
|
||||||
|
model_path=_TINY_SD3_MODEL,
|
||||||
|
modality="image",
|
||||||
|
sampling_params=sampling_params,
|
||||||
|
)
|
||||||
|
_, content = generate("tiny_sd3_encoder_fold_e2e", client)
|
||||||
|
log = ctx.log_tail(lines=500)
|
||||||
|
assert "Using native sglang backend" in log
|
||||||
|
assert "[TextEncodingStage]" in log
|
||||||
|
return image_bytes_to_numpy(content)
|
||||||
|
finally:
|
||||||
|
ctx.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncoderFoldSrtTwoGpu(CustomTestCase):
|
||||||
|
def test_folded_pipeline_matches_replicated_encoder(self):
|
||||||
|
if not current_platform.is_cuda():
|
||||||
|
self.skipTest("CUDA-only test")
|
||||||
|
if torch.cuda.device_count() < _WORLD:
|
||||||
|
self.skipTest(f"needs {_WORLD} GPUs")
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.test.test_utils import (
|
||||||
|
compute_mean_abs_diff,
|
||||||
|
compute_psnr,
|
||||||
|
compute_ssim,
|
||||||
|
)
|
||||||
|
|
||||||
|
reference = _generate_tiny_sd3(fold=False)
|
||||||
|
folded = _generate_tiny_sd3(fold=True)
|
||||||
|
ssim = compute_ssim(folded, reference)
|
||||||
|
psnr = compute_psnr(folded, reference)
|
||||||
|
mean_abs_diff = compute_mean_abs_diff(folded, reference)
|
||||||
|
print(
|
||||||
|
"ENCODER_FOLD_E2E_PARITY "
|
||||||
|
f"ssim={ssim:.6f} psnr={psnr:.6f} mad={mean_abs_diff:.6f}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
# BF16 TP reductions may move the final uint8 output slightly. A wrong
|
||||||
|
# runtime group produces multi-level pixel drift, not this rounding noise.
|
||||||
|
self.assertGreaterEqual(ssim, 0.98)
|
||||||
|
self.assertLessEqual(mean_abs_diff, 2.0)
|
||||||
|
|
||||||
|
def test_folded_srt_clip_matches_single_rank(self):
|
||||||
|
if not current_platform.is_cuda():
|
||||||
|
self.skipTest("CUDA-only test")
|
||||||
|
if torch.cuda.device_count() < _WORLD:
|
||||||
|
self.skipTest(f"needs {_WORLD} GPUs")
|
||||||
|
|
||||||
|
proc = subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"torch.distributed.run",
|
||||||
|
f"--nproc-per-node={_WORLD}",
|
||||||
|
"--master-port=29617",
|
||||||
|
__file__,
|
||||||
|
"--worker",
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=600,
|
||||||
|
)
|
||||||
|
print(proc.stdout[-4000:])
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(proc.stderr[-4000:], file=sys.stderr)
|
||||||
|
self.assertEqual(proc.returncode, 0, "folded SRT CLIP diverged")
|
||||||
|
self.assertIn("ENCODER_FOLD_SRT_PARITY PASS", proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if "--worker" in sys.argv:
|
||||||
|
raise SystemExit(_worker())
|
||||||
|
unittest.main()
|
||||||
@@ -39,7 +39,7 @@ logger = init_logger(__name__)
|
|||||||
# NPU/ascend) is read from sgl-project/ci-data-diffusion, where the GT-gen workflows
|
# NPU/ascend) is read from sgl-project/ci-data-diffusion, where the GT-gen workflows
|
||||||
# publish.
|
# publish.
|
||||||
SGL_TEST_FILES_CI_DATA_REPO = "sgl-project/ci-data-diffusion"
|
SGL_TEST_FILES_CI_DATA_REPO = "sgl-project/ci-data-diffusion"
|
||||||
SGL_TEST_FILES_CI_DATA_REVISION = "cc3f27fd2d1b4d8e1a7d5eec1247a215a502b9c1"
|
SGL_TEST_FILES_CI_DATA_REVISION = "8c3896984319c8d5628bf08df4b596baf2368ec7"
|
||||||
|
|
||||||
# The NPU pin is kept as a separate branch so ascend GT can be bumped independently
|
# The NPU pin is kept as a separate branch so ascend GT can be bumped independently
|
||||||
# when it's regenerated on its own cadence.
|
# when it's regenerated on its own cadence.
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ from sglang.multimodal_gen.configs.pipeline_configs.base import ModelTaskType
|
|||||||
from sglang.multimodal_gen.configs.pipeline_configs.flux_finetuned import (
|
from sglang.multimodal_gen.configs.pipeline_configs.flux_finetuned import (
|
||||||
Flux2FinetunedPipelineConfig,
|
Flux2FinetunedPipelineConfig,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.configs.pipeline_configs.longlive2 import (
|
||||||
|
LongLive2T2VConfig,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.configs.sample.longlive2 import LongLive2SamplingParams
|
||||||
from sglang.multimodal_gen.configs.sample.sampling_params import SamplingParams
|
from sglang.multimodal_gen.configs.sample.sampling_params import SamplingParams
|
||||||
from sglang.multimodal_gen.runtime.entrypoints.diffusion_generator import DiffGenerator
|
from sglang.multimodal_gen.runtime.entrypoints.diffusion_generator import DiffGenerator
|
||||||
from sglang.multimodal_gen.runtime.entrypoints.utils import (
|
from sglang.multimodal_gen.runtime.entrypoints.utils import (
|
||||||
@@ -49,6 +53,7 @@ from sglang.multimodal_gen.runtime.server_warmup import (
|
|||||||
from sglang.multimodal_gen.runtime.warmup_request_builder import (
|
from sglang.multimodal_gen.runtime.warmup_request_builder import (
|
||||||
DEFAULT_PLACEHOLDER_PROMPT,
|
DEFAULT_PLACEHOLDER_PROMPT,
|
||||||
SERVER_WARMUP_IMAGE_FALLBACK_RESOLUTION,
|
SERVER_WARMUP_IMAGE_FALLBACK_RESOLUTION,
|
||||||
|
_resolve_warmup_num_frames,
|
||||||
build_warmup_reqs,
|
build_warmup_reqs,
|
||||||
should_include_warmup_image,
|
should_include_warmup_image,
|
||||||
supports_synthetic_warmup,
|
supports_synthetic_warmup,
|
||||||
@@ -71,11 +76,7 @@ def _make_bare_scheduler(enable_cfg_parallel: bool) -> Scheduler:
|
|||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
server_args.is_arg_explicitly_set.return_value = False
|
server_args.is_arg_explicitly_set.return_value = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
scheduler.server_args = server_args
|
scheduler.server_args = server_args
|
||||||
scheduler.req_based_warmup_scheduled = False
|
scheduler.req_based_warmup_scheduled = False
|
||||||
@@ -270,12 +271,8 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2V
|
||||||
task_type.requires_image_input.return_value = False
|
server_args.pipeline_config.adjust_num_frames.side_effect = lambda value: value
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2V.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
generator.server_args = server_args
|
generator.server_args = server_args
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(num_frames=81, num_inference_steps=50)
|
sampling_defaults = SamplingParams(num_frames=81, num_inference_steps=50)
|
||||||
@@ -307,12 +304,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
negative_prompt="model default negative",
|
negative_prompt="model default negative",
|
||||||
@@ -348,12 +340,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(width=640, height=640)
|
sampling_defaults = SamplingParams(width=640, height=640)
|
||||||
with patch(
|
with patch(
|
||||||
@@ -376,12 +363,8 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2V
|
||||||
task_type.requires_image_input.return_value = False
|
server_args.pipeline_config.adjust_num_frames.side_effect = lambda value: value
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2V.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
negative_prompt="model default negative",
|
negative_prompt="model default negative",
|
||||||
@@ -418,12 +401,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
width=1024,
|
width=1024,
|
||||||
@@ -452,12 +430,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
server_args.backend = "auto"
|
server_args.backend = "auto"
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(width=1024, height=1024)
|
sampling_defaults = SamplingParams(width=1024, height=1024)
|
||||||
with patch(
|
with patch(
|
||||||
@@ -481,12 +454,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
server_args.backend = "diffusers"
|
server_args.backend = "diffusers"
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(width=1024, height=1024)
|
sampling_defaults = SamplingParams(width=1024, height=1024)
|
||||||
with patch(
|
with patch(
|
||||||
@@ -507,12 +475,8 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2V
|
||||||
task_type.requires_image_input.return_value = False
|
server_args.pipeline_config.adjust_num_frames.side_effect = lambda value: value
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2V.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
width=832,
|
width=832,
|
||||||
@@ -533,18 +497,35 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
self.assertEqual(reqs[0].num_inference_steps, 2)
|
self.assertEqual(reqs[0].num_inference_steps, 2)
|
||||||
self.assertEqual(reqs[0].num_frames, 17)
|
self.assertEqual(reqs[0].num_frames, 17)
|
||||||
|
|
||||||
|
def test_video_warmup_preserves_model_frame_alignment(self):
|
||||||
|
pipeline_config = LongLive2T2VConfig()
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=pipeline_config,
|
||||||
|
enable_breakable_cuda_graph=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
num_frames = _resolve_warmup_num_frames(
|
||||||
|
server_args,
|
||||||
|
LongLive2SamplingParams(),
|
||||||
|
server_based_warmup=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
temporal_scale = pipeline_config.vae_config.arch_config.scale_factor_temporal
|
||||||
|
latent_frames = (num_frames - 1) // temporal_scale + 1
|
||||||
|
self.assertEqual(num_frames, 29)
|
||||||
|
self.assertEqual(
|
||||||
|
latent_frames % pipeline_config.dit_config.arch_config.num_frames_per_block,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
def test_server_based_warmup_uses_video_supported_resolution_budget(self):
|
def test_server_based_warmup_uses_video_supported_resolution_budget(self):
|
||||||
server_args = MagicMock()
|
server_args = MagicMock()
|
||||||
server_args.warmup_steps = 1
|
server_args.warmup_steps = 1
|
||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2V
|
||||||
task_type.requires_image_input.return_value = False
|
server_args.pipeline_config.adjust_num_frames.side_effect = lambda value: value
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2V.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
width=1280,
|
width=1280,
|
||||||
@@ -580,13 +561,9 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
server_args.pipeline_class_name = "LTX2TwoStageHQPipeline"
|
server_args.pipeline_class_name = "LTX2TwoStageHQPipeline"
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2V
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = False
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2V.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
server_args.pipeline_config.vae_scale_factor = 32
|
server_args.pipeline_config.vae_scale_factor = 32
|
||||||
|
server_args.pipeline_config.adjust_num_frames.side_effect = lambda value: value
|
||||||
|
|
||||||
sampling_defaults = SamplingParams(
|
sampling_defaults = SamplingParams(
|
||||||
width=1920,
|
width=1920,
|
||||||
@@ -614,12 +591,7 @@ class TestWarmupReqCfgParallel(unittest.TestCase):
|
|||||||
server_args.enable_cfg_parallel = False
|
server_args.enable_cfg_parallel = False
|
||||||
server_args.enable_torch_compile = False
|
server_args.enable_torch_compile = False
|
||||||
|
|
||||||
task_type = MagicMock()
|
server_args.pipeline_config.task_type = ModelTaskType.T2I
|
||||||
task_type.requires_image_input.return_value = False
|
|
||||||
task_type.accepts_image_input.return_value = False
|
|
||||||
task_type.is_image_gen.return_value = True
|
|
||||||
task_type.data_type.return_value = ModelTaskType.T2I.data_type()
|
|
||||||
server_args.pipeline_config.task_type = task_type
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"sglang.multimodal_gen.runtime.warmup_request_builder.get_model_sampling_defaults",
|
"sglang.multimodal_gen.runtime.warmup_request_builder.get_model_sampling_defaults",
|
||||||
|
|||||||
@@ -159,20 +159,50 @@ def test_srt_owned_groups_are_not_overwritten_or_cleared():
|
|||||||
|
|
||||||
|
|
||||||
def test_srt_tp_groups_follow_encoder_folding_context():
|
def test_srt_tp_groups_follow_encoder_folding_context():
|
||||||
original_tp_group = object()
|
original_diffusion_tp_group = object()
|
||||||
|
original_srt_tp_group = object()
|
||||||
|
original_srt_attention_tp_group = object()
|
||||||
folding_tp_group = object()
|
folding_tp_group = object()
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(parallel_state, "_TP", original_tp_group),
|
patch.object(parallel_state, "_TP", original_diffusion_tp_group),
|
||||||
patch.object(parallel_state, "_TP_STATE_PATCHED", False),
|
patch.object(srt_parallel_state, "_TP", original_srt_tp_group),
|
||||||
patch.object(srt_parallel_state, "_TP", original_tp_group),
|
patch.object(
|
||||||
patch.object(srt_parallel_state, "_ATTN_TP", original_tp_group),
|
srt_parallel_state,
|
||||||
|
"_ATTN_TP",
|
||||||
|
original_srt_attention_tp_group,
|
||||||
|
),
|
||||||
):
|
):
|
||||||
with parallel_state.patch_tensor_parallel_group(folding_tp_group):
|
with parallel_state.use_tensor_parallel_group(folding_tp_group):
|
||||||
assert parallel_state._TP is folding_tp_group
|
assert parallel_state._TP is folding_tp_group
|
||||||
assert srt_parallel_state._TP is folding_tp_group
|
assert srt_parallel_state._TP is folding_tp_group
|
||||||
assert srt_parallel_state._ATTN_TP is folding_tp_group
|
assert srt_parallel_state._ATTN_TP is folding_tp_group
|
||||||
|
|
||||||
|
assert parallel_state._TP is original_diffusion_tp_group
|
||||||
|
assert srt_parallel_state._TP is original_srt_tp_group
|
||||||
|
assert srt_parallel_state._ATTN_TP is original_srt_attention_tp_group
|
||||||
|
|
||||||
|
|
||||||
|
def test_encoder_folding_context_is_nested_and_restores_each_group():
|
||||||
|
original_tp_group = object()
|
||||||
|
outer_tp_group = object()
|
||||||
|
inner_tp_group = object()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(parallel_state, "_TP", original_tp_group),
|
||||||
|
patch.object(srt_parallel_state, "_TP", original_tp_group),
|
||||||
|
patch.object(srt_parallel_state, "_ATTN_TP", original_tp_group),
|
||||||
|
):
|
||||||
|
with parallel_state.use_tensor_parallel_group(outer_tp_group):
|
||||||
|
with parallel_state.use_tensor_parallel_group(inner_tp_group):
|
||||||
|
assert parallel_state._TP is inner_tp_group
|
||||||
|
assert srt_parallel_state._TP is inner_tp_group
|
||||||
|
assert srt_parallel_state._ATTN_TP is inner_tp_group
|
||||||
|
|
||||||
|
assert parallel_state._TP is outer_tp_group
|
||||||
|
assert srt_parallel_state._TP is outer_tp_group
|
||||||
|
assert srt_parallel_state._ATTN_TP is outer_tp_group
|
||||||
|
|
||||||
assert parallel_state._TP is original_tp_group
|
assert parallel_state._TP is original_tp_group
|
||||||
assert srt_parallel_state._TP is original_tp_group
|
assert srt_parallel_state._TP is original_tp_group
|
||||||
assert srt_parallel_state._ATTN_TP is original_tp_group
|
assert srt_parallel_state._ATTN_TP is original_tp_group
|
||||||
|
|||||||
@@ -5,9 +5,12 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from urllib.error import URLError
|
||||||
|
|
||||||
from sglang.multimodal_gen.runtime.entrypoints import http_server
|
from sglang.multimodal_gen.runtime.entrypoints import http_server
|
||||||
from sglang.multimodal_gen.runtime.entrypoints.http_server import (
|
from sglang.multimodal_gen.runtime.entrypoints.http_server import (
|
||||||
@@ -15,6 +18,7 @@ from sglang.multimodal_gen.runtime.entrypoints.http_server import (
|
|||||||
health_generate,
|
health_generate,
|
||||||
liveness,
|
liveness,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.test.server.test_server_utils import ServerManager
|
||||||
|
|
||||||
|
|
||||||
def _make_request(warmup_done) -> SimpleNamespace:
|
def _make_request(warmup_done) -> SimpleNamespace:
|
||||||
@@ -90,5 +94,46 @@ class TestWaitUntilHttpLive(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertEqual(fake_client.urls, ["http://127.0.0.1:11000/liveness"] * 2)
|
self.assertEqual(fake_client.urls, ["http://127.0.0.1:11000/liveness"] * 2)
|
||||||
|
|
||||||
|
|
||||||
|
class _ReadyResponse:
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *exc_info):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class _RunningProcess:
|
||||||
|
returncode = None
|
||||||
|
|
||||||
|
def poll(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class TestServerManagerReadiness(unittest.TestCase):
|
||||||
|
def test_waits_for_health_after_http_startup(self):
|
||||||
|
manager = ServerManager("test-model", port=11000, wait_deadline=1)
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
stdout_path = Path(temp_dir) / "server.log"
|
||||||
|
stdout_path.write_text("Application startup complete.\n", encoding="utf-8")
|
||||||
|
with (
|
||||||
|
mock.patch(
|
||||||
|
"sglang.multimodal_gen.test.server.test_server_utils.urlopen",
|
||||||
|
side_effect=[URLError("warming up"), _ReadyResponse()],
|
||||||
|
) as health_request,
|
||||||
|
mock.patch(
|
||||||
|
"sglang.multimodal_gen.test.server.test_server_utils.time.sleep"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
manager._wait_for_ready(_RunningProcess(), stdout_path)
|
||||||
|
|
||||||
|
self.assertEqual(health_request.call_count, 2)
|
||||||
|
self.assertEqual(
|
||||||
|
[call.args[0] for call in health_request.call_args_list],
|
||||||
|
["http://127.0.0.1:11000/health"] * 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders import clip as mmgen_clip
|
||||||
|
from sglang.srt.models import clip as srt_clip
|
||||||
|
|
||||||
|
|
||||||
|
def _clip_config():
|
||||||
|
return SimpleNamespace(
|
||||||
|
hidden_size=16,
|
||||||
|
intermediate_size=32,
|
||||||
|
num_attention_heads=2,
|
||||||
|
num_hidden_layers=1,
|
||||||
|
layer_norm_eps=1e-5,
|
||||||
|
hidden_act="quick_gelu",
|
||||||
|
vocab_size=32,
|
||||||
|
max_position_embeddings=8,
|
||||||
|
eos_token_id=2,
|
||||||
|
output_hidden_states=False,
|
||||||
|
attention_dropout=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeQKV(nn.Module):
|
||||||
|
def forward(self, hidden_states):
|
||||||
|
return torch.cat((hidden_states, hidden_states, hidden_states), dim=-1), None
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeProjection(nn.Module):
|
||||||
|
def forward(self, hidden_states):
|
||||||
|
return hidden_states, None
|
||||||
|
|
||||||
|
|
||||||
|
def test_mmgen_clip_reuses_srt_components():
|
||||||
|
assert mmgen_clip.CLIPEncoder is srt_clip.CLIPEncoder
|
||||||
|
assert mmgen_clip.CLIPTextEmbeddings is srt_clip.CLIPTextEmbeddings
|
||||||
|
assert mmgen_clip.CLIPVisionEmbeddings is srt_clip.CLIPVisionEmbeddings
|
||||||
|
|
||||||
|
|
||||||
|
def test_clip_encoder_propagates_causal_semantics():
|
||||||
|
with (
|
||||||
|
patch.object(srt_clip, "CLIPAttention", return_value=nn.Identity()) as attn,
|
||||||
|
patch.object(srt_clip, "CLIPMLP", return_value=nn.Identity()),
|
||||||
|
):
|
||||||
|
srt_clip.CLIPEncoder(_clip_config(), causal=True)
|
||||||
|
|
||||||
|
assert attn.call_args.kwargs["causal"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_mmgen_text_clip_requests_masked_srt_attention():
|
||||||
|
with patch.object(mmgen_clip, "CLIPEncoder", return_value=nn.Identity()) as encoder:
|
||||||
|
mmgen_clip.CLIPTextTransformer(_clip_config(), prefix="text_model.encoder")
|
||||||
|
|
||||||
|
assert encoder.call_args.kwargs["causal"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_clip_attention_separates_text_and_vision_semantics():
|
||||||
|
parallel = SimpleNamespace(attn_tp_size=1, attn_tp_rank=0)
|
||||||
|
hidden_states = torch.randn(2, 3, 16)
|
||||||
|
padding_mask = torch.zeros(2, 1, 3, 3)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(srt_clip, "get_parallel", return_value=parallel),
|
||||||
|
patch.object(srt_clip, "QKVParallelLinear", return_value=_FakeQKV()),
|
||||||
|
patch.object(srt_clip, "RowParallelLinear", return_value=_FakeProjection()),
|
||||||
|
patch.object(
|
||||||
|
srt_clip.F,
|
||||||
|
"scaled_dot_product_attention",
|
||||||
|
side_effect=lambda query, key, value, **kwargs: query,
|
||||||
|
) as sdpa,
|
||||||
|
):
|
||||||
|
text_attention = srt_clip.CLIPAttention(_clip_config(), causal=True)
|
||||||
|
vision_attention = srt_clip.CLIPAttention(_clip_config())
|
||||||
|
text_attention(hidden_states)
|
||||||
|
text_attention(hidden_states, attention_mask=padding_mask)
|
||||||
|
vision_attention(hidden_states)
|
||||||
|
|
||||||
|
assert sdpa.call_args_list[0].kwargs["is_causal"] is True
|
||||||
|
assert sdpa.call_args_list[1].kwargs["is_causal"] is False
|
||||||
|
assert sdpa.call_args_list[1].kwargs["attn_mask"] is padding_mask
|
||||||
|
assert sdpa.call_args_list[2].kwargs["is_causal"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_clip_attention_mask_combines_causal_and_padding_masks():
|
||||||
|
mask = srt_clip.prepare_clip_attention_mask(
|
||||||
|
torch.Size((1, 3)),
|
||||||
|
torch.float32,
|
||||||
|
torch.device("cpu"),
|
||||||
|
torch.tensor([[1, 1, 0]]),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mask.shape == (1, 1, 3, 3)
|
||||||
|
assert mask[0, 0, 0, 0] == 0
|
||||||
|
assert mask[0, 0, 0, 1] < -1e20
|
||||||
|
assert torch.all(mask[..., 2] < -1e20)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_clip_attention_mask_keeps_unmasked_fast_path():
|
||||||
|
assert (
|
||||||
|
srt_clip.prepare_clip_attention_mask(
|
||||||
|
torch.Size((2, 3)), torch.float32, torch.device("cpu")
|
||||||
|
)
|
||||||
|
is None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_srt_clip_weight_name_mapping():
|
||||||
|
assert (
|
||||||
|
mmgen_clip._srt_clip_param_name(
|
||||||
|
"text_model.encoder.layers.0.self_attn.out_proj.weight"
|
||||||
|
)
|
||||||
|
== "text_model.encoder.layers.0.self_attn.proj.weight"
|
||||||
|
)
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
from contextlib import contextmanager
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -6,6 +5,9 @@ from torch import nn
|
|||||||
|
|
||||||
from sglang.multimodal_gen.runtime.loader.utils import get_param_names_mapping
|
from sglang.multimodal_gen.runtime.loader.utils import get_param_names_mapping
|
||||||
from sglang.multimodal_gen.runtime.models.encoders import gemma_3
|
from sglang.multimodal_gen.runtime.models.encoders import gemma_3
|
||||||
|
from sglang.multimodal_gen.runtime.models.encoders.base import (
|
||||||
|
EncoderTensorParallelMixin,
|
||||||
|
)
|
||||||
from sglang.srt.models import siglip
|
from sglang.srt.models import siglip
|
||||||
|
|
||||||
|
|
||||||
@@ -35,10 +37,8 @@ def test_siglip_encoder_propagates_attention_backend():
|
|||||||
|
|
||||||
def test_gemma3_uses_srt_siglip_with_stable_backend():
|
def test_gemma3_uses_srt_siglip_with_stable_backend():
|
||||||
config = SimpleNamespace(vision_config=object(), text_config=object())
|
config = SimpleNamespace(vision_config=object(), text_config=object())
|
||||||
folding_group = object()
|
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(gemma_3, "get_tp_group", return_value=folding_group),
|
|
||||||
patch.object(
|
patch.object(
|
||||||
gemma_3,
|
gemma_3,
|
||||||
"SiglipVisionModel",
|
"SiglipVisionModel",
|
||||||
@@ -63,42 +63,8 @@ def test_gemma3_uses_srt_siglip_with_stable_backend():
|
|||||||
quant_config=None,
|
quant_config=None,
|
||||||
prefix="vision_tower",
|
prefix="vision_tower",
|
||||||
)
|
)
|
||||||
assert model._vision_tensor_parallel_group is folding_group
|
assert isinstance(model, EncoderTensorParallelMixin)
|
||||||
|
assert not hasattr(model, "_vision_tensor_parallel_group")
|
||||||
|
|
||||||
def test_gemma3_restores_vision_tensor_parallel_group():
|
|
||||||
model = gemma_3.Gemma3ForConditionalGeneration.__new__(
|
|
||||||
gemma_3.Gemma3ForConditionalGeneration
|
|
||||||
)
|
|
||||||
nn.Module.__init__(model)
|
|
||||||
folding_group = object()
|
|
||||||
active_group = object()
|
|
||||||
model._vision_tensor_parallel_group = folding_group
|
|
||||||
events = []
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def use_group(group):
|
|
||||||
events.append(("enter", group))
|
|
||||||
yield
|
|
||||||
events.append(("exit", group))
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch.object(gemma_3, "get_tp_group", return_value=active_group),
|
|
||||||
patch.object(
|
|
||||||
gemma_3,
|
|
||||||
"patch_tensor_parallel_group",
|
|
||||||
side_effect=use_group,
|
|
||||||
) as patch_group,
|
|
||||||
):
|
|
||||||
with model._vision_parallel_context():
|
|
||||||
events.append(("forward", folding_group))
|
|
||||||
|
|
||||||
patch_group.assert_called_once_with(folding_group)
|
|
||||||
assert events == [
|
|
||||||
("enter", folding_group),
|
|
||||||
("forward", folding_group),
|
|
||||||
("exit", folding_group),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_gemma3_maps_hf_siglip_projection_name():
|
def test_gemma3_maps_hf_siglip_projection_name():
|
||||||
|
|||||||
@@ -6,21 +6,48 @@ from typing import Iterable, List, Optional, Tuple, Type, Union
|
|||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
from transformers import CLIPConfig, CLIPTextConfig, CLIPVisionConfig
|
from transformers import CLIPConfig, CLIPTextConfig, CLIPVisionConfig
|
||||||
from transformers.modeling_attn_mask_utils import _create_4d_causal_attention_mask
|
|
||||||
|
|
||||||
from sglang.srt.layers.activation import QuickGELU
|
from sglang.srt.layers.activation import QuickGELU, get_act_fn
|
||||||
from sglang.srt.layers.attention.vision import VisionAttention
|
|
||||||
from sglang.srt.layers.conv import Conv2dLayer
|
from sglang.srt.layers.conv import Conv2dLayer
|
||||||
from sglang.srt.layers.linear import ColumnParallelLinear, RowParallelLinear
|
from sglang.srt.layers.linear import (
|
||||||
|
ColumnParallelLinear,
|
||||||
|
QKVParallelLinear,
|
||||||
|
RowParallelLinear,
|
||||||
|
)
|
||||||
from sglang.srt.layers.pooler import EmbeddingPoolerOutput, Pooler, PoolingType
|
from sglang.srt.layers.pooler import EmbeddingPoolerOutput, Pooler, PoolingType
|
||||||
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
||||||
from sglang.srt.managers.schedule_batch import MultimodalInputs
|
from sglang.srt.managers.schedule_batch import MultimodalInputs
|
||||||
from sglang.srt.model_executor.model_runner import ForwardBatch
|
from sglang.srt.model_executor.model_runner import ForwardBatch
|
||||||
from sglang.srt.model_loader.weight_utils import default_weight_loader
|
from sglang.srt.model_loader.weight_utils import default_weight_loader
|
||||||
|
from sglang.srt.runtime_context import get_parallel
|
||||||
from sglang.srt.utils import add_prefix, flatten_nested_list
|
from sglang.srt.utils import add_prefix, flatten_nested_list
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_clip_attention_mask(
|
||||||
|
input_shape: torch.Size,
|
||||||
|
dtype: torch.dtype,
|
||||||
|
device: torch.device,
|
||||||
|
attention_mask: Optional[torch.Tensor] = None,
|
||||||
|
) -> Optional[torch.Tensor]:
|
||||||
|
if attention_mask is None:
|
||||||
|
return None
|
||||||
|
batch_size, sequence_length = input_shape
|
||||||
|
causal_mask = torch.full(
|
||||||
|
(sequence_length, sequence_length),
|
||||||
|
torch.finfo(dtype).min,
|
||||||
|
dtype=dtype,
|
||||||
|
device=device,
|
||||||
|
)
|
||||||
|
causal_mask = torch.triu(causal_mask, diagonal=1)
|
||||||
|
causal_mask = causal_mask[None, None].expand(batch_size, 1, -1, -1)
|
||||||
|
if attention_mask.dim() == 2:
|
||||||
|
attention_mask = attention_mask[:, None, None, :].to(dtype=dtype)
|
||||||
|
attention_mask = (1.0 - attention_mask) * torch.finfo(dtype).min
|
||||||
|
return causal_mask + attention_mask
|
||||||
|
|
||||||
|
|
||||||
class CLIPVisionEmbeddings(nn.Module):
|
class CLIPVisionEmbeddings(nn.Module):
|
||||||
|
|
||||||
def __init__(self, config: CLIPVisionConfig):
|
def __init__(self, config: CLIPVisionConfig):
|
||||||
@@ -88,8 +115,17 @@ class CLIPTextEmbeddings(nn.Module):
|
|||||||
position_ids: Optional[torch.LongTensor] = None,
|
position_ids: Optional[torch.LongTensor] = None,
|
||||||
inputs_embeds: Optional[torch.FloatTensor] = None,
|
inputs_embeds: Optional[torch.FloatTensor] = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
seq_length = (
|
if input_ids is not None:
|
||||||
input_ids.shape[-1] if input_ids is not None else inputs_embeds.shape[-2]
|
seq_length = input_ids.shape[-1]
|
||||||
|
elif inputs_embeds is not None:
|
||||||
|
seq_length = inputs_embeds.shape[-2]
|
||||||
|
else:
|
||||||
|
raise ValueError("Either input_ids or inputs_embeds must be provided.")
|
||||||
|
|
||||||
|
max_positions = self.position_embedding.weight.shape[0]
|
||||||
|
if seq_length > max_positions:
|
||||||
|
raise ValueError(
|
||||||
|
f"Sequence length {seq_length} exceeds the maximum {max_positions}."
|
||||||
)
|
)
|
||||||
|
|
||||||
if position_ids is None:
|
if position_ids is None:
|
||||||
@@ -109,7 +145,7 @@ class CLIPMLP(nn.Module):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config,
|
config,
|
||||||
act_layer: Type[nn.Module] = QuickGELU,
|
act_layer: Optional[Type[nn.Module]] = None,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
):
|
):
|
||||||
@@ -120,7 +156,12 @@ class CLIPMLP(nn.Module):
|
|||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=add_prefix("fc1", prefix),
|
prefix=add_prefix("fc1", prefix),
|
||||||
)
|
)
|
||||||
|
if act_layer is not None:
|
||||||
self.act = act_layer()
|
self.act = act_layer()
|
||||||
|
elif config.hidden_act == "quick_gelu":
|
||||||
|
self.act = QuickGELU()
|
||||||
|
else:
|
||||||
|
self.act = get_act_fn(config.hidden_act)
|
||||||
self.fc2 = RowParallelLinear(
|
self.fc2 = RowParallelLinear(
|
||||||
config.intermediate_size,
|
config.intermediate_size,
|
||||||
config.hidden_size,
|
config.hidden_size,
|
||||||
@@ -135,29 +176,90 @@ class CLIPMLP(nn.Module):
|
|||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPAttention(nn.Module):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: Union[CLIPTextConfig, CLIPVisionConfig],
|
||||||
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
|
prefix: str = "",
|
||||||
|
causal: bool = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
parallel = get_parallel()
|
||||||
|
self.num_heads = config.num_attention_heads // parallel.attn_tp_size
|
||||||
|
self.head_dim = config.hidden_size // config.num_attention_heads
|
||||||
|
self.causal = causal
|
||||||
|
self.dropout = config.attention_dropout
|
||||||
|
self.scale = self.head_dim**-0.5
|
||||||
|
self.qkv_proj = QKVParallelLinear(
|
||||||
|
hidden_size=config.hidden_size,
|
||||||
|
head_size=self.head_dim,
|
||||||
|
total_num_heads=config.num_attention_heads,
|
||||||
|
bias=True,
|
||||||
|
quant_config=quant_config,
|
||||||
|
prefix=add_prefix("qkv_proj", prefix),
|
||||||
|
tp_rank=parallel.attn_tp_rank,
|
||||||
|
tp_size=parallel.attn_tp_size,
|
||||||
|
)
|
||||||
|
self.proj = RowParallelLinear(
|
||||||
|
input_size=config.hidden_size,
|
||||||
|
output_size=config.hidden_size,
|
||||||
|
bias=True,
|
||||||
|
quant_config=quant_config,
|
||||||
|
prefix=add_prefix("proj", prefix),
|
||||||
|
tp_rank=parallel.attn_tp_rank,
|
||||||
|
tp_size=parallel.attn_tp_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
def forward(
|
||||||
|
self,
|
||||||
|
hidden_states: torch.Tensor,
|
||||||
|
attention_mask: Optional[torch.Tensor] = None,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
batch_size, sequence_length, _ = hidden_states.shape
|
||||||
|
qkv, _ = self.qkv_proj(hidden_states)
|
||||||
|
query, key, value = qkv.chunk(3, dim=-1)
|
||||||
|
qkv_shape = (batch_size, sequence_length, self.num_heads, self.head_dim)
|
||||||
|
query = query.view(qkv_shape).transpose(1, 2)
|
||||||
|
key = key.view(qkv_shape).transpose(1, 2)
|
||||||
|
value = value.view(qkv_shape).transpose(1, 2)
|
||||||
|
output = F.scaled_dot_product_attention(
|
||||||
|
query,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
attn_mask=attention_mask,
|
||||||
|
dropout_p=self.dropout if self.training else 0.0,
|
||||||
|
is_causal=self.causal and attention_mask is None,
|
||||||
|
scale=self.scale,
|
||||||
|
)
|
||||||
|
output = output.transpose(1, 2).reshape(
|
||||||
|
batch_size, sequence_length, self.num_heads * self.head_dim
|
||||||
|
)
|
||||||
|
output, _ = self.proj(output)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
class CLIPEncoderLayer(nn.Module):
|
class CLIPEncoderLayer(nn.Module):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: CLIPVisionConfig,
|
config: CLIPVisionConfig,
|
||||||
act_layer: Type[nn.Module] = QuickGELU,
|
act_layer: Optional[Type[nn.Module]] = None,
|
||||||
norm_layer: Type[nn.Module] = None,
|
norm_layer: Type[nn.Module] = None,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
|
causal: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if norm_layer is None:
|
if norm_layer is None:
|
||||||
norm_layer = partial(nn.LayerNorm, eps=config.layer_norm_eps)
|
norm_layer = partial(nn.LayerNorm, eps=config.layer_norm_eps)
|
||||||
self.layer_norm1 = norm_layer(config.hidden_size)
|
self.layer_norm1 = norm_layer(config.hidden_size)
|
||||||
self.layer_norm2 = norm_layer(config.hidden_size)
|
self.layer_norm2 = norm_layer(config.hidden_size)
|
||||||
self.self_attn = VisionAttention(
|
self.self_attn = CLIPAttention(
|
||||||
embed_dim=config.hidden_size,
|
config,
|
||||||
num_heads=config.num_attention_heads,
|
|
||||||
projection_size=config.hidden_size,
|
|
||||||
use_qkv_parallel=True,
|
|
||||||
flatten_batch=True,
|
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=add_prefix("self_attn", prefix),
|
prefix=add_prefix("self_attn", prefix),
|
||||||
|
causal=causal,
|
||||||
)
|
)
|
||||||
self.mlp = CLIPMLP(
|
self.mlp = CLIPMLP(
|
||||||
config,
|
config,
|
||||||
@@ -210,20 +312,29 @@ class CLIPEncoder(nn.Module):
|
|||||||
config: CLIPVisionConfig,
|
config: CLIPVisionConfig,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
|
num_hidden_layers_override: Optional[int] = None,
|
||||||
|
act_layer: Optional[Type[nn.Module]] = None,
|
||||||
|
causal: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
num_hidden_layers = config.num_hidden_layers
|
num_hidden_layers = (
|
||||||
|
config.num_hidden_layers
|
||||||
|
if num_hidden_layers_override is None
|
||||||
|
else num_hidden_layers_override
|
||||||
|
)
|
||||||
norm_layer = partial(nn.LayerNorm, eps=config.layer_norm_eps)
|
norm_layer = partial(nn.LayerNorm, eps=config.layer_norm_eps)
|
||||||
self.layers = nn.ModuleList(
|
self.layers = nn.ModuleList(
|
||||||
[
|
[
|
||||||
CLIPEncoderLayer(
|
CLIPEncoderLayer(
|
||||||
config=config,
|
config=config,
|
||||||
|
act_layer=act_layer,
|
||||||
norm_layer=norm_layer,
|
norm_layer=norm_layer,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=add_prefix(f"layers.{layer_idx}", prefix),
|
prefix=add_prefix(f"layers.{layer_idx}", prefix),
|
||||||
|
causal=causal,
|
||||||
)
|
)
|
||||||
for layer_idx in range(num_hidden_layers)
|
for layer_idx in range(num_hidden_layers)
|
||||||
]
|
]
|
||||||
@@ -265,6 +376,7 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
config=config,
|
config=config,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=add_prefix("encoder", prefix),
|
prefix=add_prefix("encoder", prefix),
|
||||||
|
causal=True,
|
||||||
)
|
)
|
||||||
self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
self.final_layer_norm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
||||||
|
|
||||||
@@ -281,12 +393,13 @@ class CLIPTextTransformer(nn.Module):
|
|||||||
input_shape = input_ids.size()
|
input_shape = input_ids.size()
|
||||||
input_ids = input_ids.view(-1, input_shape[-1])
|
input_ids = input_ids.view(-1, input_shape[-1])
|
||||||
hidden_states = self.embeddings(input_ids, position_ids)
|
hidden_states = self.embeddings(input_ids, position_ids)
|
||||||
causal_attention_mask = _create_4d_causal_attention_mask(
|
attention_mask = prepare_clip_attention_mask(
|
||||||
input_ids.shape, hidden_states.dtype, device=hidden_states.device
|
input_ids.shape,
|
||||||
)
|
hidden_states.dtype,
|
||||||
encoder_outputs = self.encoder(
|
hidden_states.device,
|
||||||
hidden_states, attention_mask, causal_attention_mask
|
attention_mask,
|
||||||
)
|
)
|
||||||
|
encoder_outputs = self.encoder(hidden_states, attention_mask=attention_mask)
|
||||||
last_hidden_state = self.final_layer_norm(encoder_outputs)
|
last_hidden_state = self.final_layer_norm(encoder_outputs)
|
||||||
return last_hidden_state
|
return last_hidden_state
|
||||||
|
|
||||||
@@ -311,7 +424,7 @@ class CLIPTextModel(nn.Module):
|
|||||||
input_ids: torch.Tensor,
|
input_ids: torch.Tensor,
|
||||||
position_ids: torch.Tensor,
|
position_ids: torch.Tensor,
|
||||||
):
|
):
|
||||||
return self.text_model(input_ids, position_ids)
|
return self.text_model(input_ids, position_ids=position_ids)
|
||||||
|
|
||||||
|
|
||||||
class CLIPVisionTransformer(nn.Module):
|
class CLIPVisionTransformer(nn.Module):
|
||||||
|
|||||||
Reference in New Issue
Block a user