[Feature] support sp for hunyuan (#2163)
Signed-off-by: dengyunyang <584797741@qq.com> Signed-off-by: 汪志鹏 <wangzhipeng628@gmail.com> Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com>
This commit is contained in:
1 parent
c139ee4aed
commit
c0d90b64c6
6 files changed
+346
-27
No files matched your search
@@ -237,6 +237,11 @@ def parse_args() -> argparse.Namespace:
|
||||
action="store_true",
|
||||
help="Enable diffusion pipeline profiler to display stage durations.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-stats",
|
||||
action="store_true",
|
||||
help="Enable logging of diffusion pipeline stats.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -326,6 +331,7 @@ def main():
|
||||
"enforce_eager": args.enforce_eager,
|
||||
"enable_cpu_offload": args.enable_cpu_offload,
|
||||
"mode": "text-to-image",
|
||||
"log_stats": args.log_stats,
|
||||
"enable_diffusion_pipeline_profiler": args.enable_diffusion_pipeline_profiler,
|
||||
**lora_args,
|
||||
**quant_kwargs,
|
||||
|
||||
@@ -373,8 +373,14 @@ class UlyssesParallelAttention:
|
||||
orig_head_cnt=int(orig_head_cnt) if mode == "advanced_uaa" else 0,
|
||||
joint_orig_head_cnt=int(joint_orig_head_cnt) if mode == "advanced_uaa" else 0,
|
||||
)
|
||||
|
||||
use_2d_mask = False
|
||||
if attn_metadata is not None:
|
||||
if attn_metadata.attn_mask is not None and attn_metadata.attn_mask.ndim == 2:
|
||||
use_2d_mask = True
|
||||
if attn_metadata.joint_attn_mask is not None and attn_metadata.joint_attn_mask.ndim == 2:
|
||||
use_2d_mask = True
|
||||
|
||||
if attn_metadata is not None and use_2d_mask:
|
||||
if is_joint:
|
||||
if attn_metadata.joint_attn_mask is None and attn_metadata.attn_mask is None:
|
||||
attn_metadata.attn_mask = None
|
||||
|
||||
@@ -242,6 +242,7 @@ class SequenceParallelSplitHook(ModelHook):
|
||||
|
||||
if not is_tensor and not is_tensor_list:
|
||||
# No tensor outputs to shard
|
||||
logger.warning_once("No tensor outputs to shard.")
|
||||
return output
|
||||
|
||||
output_list = [output] if is_tensor else list(output)
|
||||
|
||||
@@ -11,7 +11,6 @@ from typing import Any, cast
|
||||
import numpy as np
|
||||
import regex as re
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
|
||||
@@ -57,8 +56,19 @@ from vllm.model_executor.models.utils import (
|
||||
)
|
||||
from vllm.v1.attention.backend import AttentionType
|
||||
|
||||
from vllm_omni.diffusion.attention.backends.abstract import (
|
||||
AttentionMetadata,
|
||||
)
|
||||
from vllm_omni.diffusion.attention.layer import Attention
|
||||
from vllm_omni.diffusion.distributed.parallel_state import get_pp_group
|
||||
from vllm_omni.diffusion.distributed.parallel_state import (
|
||||
get_pp_group,
|
||||
get_sequence_parallel_rank,
|
||||
get_sequence_parallel_world_size,
|
||||
)
|
||||
from vllm_omni.diffusion.distributed.sp_plan import (
|
||||
SequenceParallelInput,
|
||||
SequenceParallelOutput,
|
||||
)
|
||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||
from vllm_omni.diffusion.layers.rope import RotaryEmbedding
|
||||
from vllm_omni.diffusion.models.hunyuan_image_3.hunyuan_fused_moe import HunyuanFusedMoE
|
||||
@@ -411,14 +421,18 @@ def apply_rotary_pos_emb(
|
||||
|
||||
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
|
||||
"""
|
||||
This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
|
||||
num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
|
||||
Equivalent to torch.repeat_interleave(x, dim=2, repeats=n_rep).
|
||||
Input: (batch, seqlen, num_key_value_heads, head_dim)
|
||||
Output: (batch, seqlen, num_attention_heads, head_dim)
|
||||
"""
|
||||
batch, num_key_value_heads, slen, head_dim = hidden_states.shape
|
||||
batch, slen, num_key_value_heads, head_dim = hidden_states.shape
|
||||
|
||||
if n_rep == 1:
|
||||
return hidden_states
|
||||
hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
|
||||
return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
|
||||
|
||||
hidden_states = hidden_states[:, :, :, None, :].expand(batch, slen, num_key_value_heads, n_rep, head_dim)
|
||||
|
||||
return hidden_states.reshape(batch, slen, num_key_value_heads * n_rep, head_dim)
|
||||
|
||||
|
||||
def default(value, default_value):
|
||||
@@ -830,15 +844,31 @@ class ImageKVCacheManager:
|
||||
Manages specialized caching and updating of KV-Cache for image tokens in multimodal models.
|
||||
"""
|
||||
|
||||
def __init__(self, image_token_len: int = 4097):
|
||||
def __init__(self, num_heads: int, num_kv_heads: int, head_dim: int, scaling: float, image_token_len: int = 4097):
|
||||
"""
|
||||
Args:
|
||||
image_token_len: Number of tokens per image (including special placeholders),
|
||||
default 4097 (timestamp + 4096 image tokens).
|
||||
"""
|
||||
# attention related
|
||||
self.num_heads = num_heads
|
||||
self.num_kv_heads = num_kv_heads
|
||||
self.head_dim = head_dim
|
||||
self.scaling = scaling
|
||||
# cache related
|
||||
self.image_token_len: int = image_token_len
|
||||
self.image_kv_cache: tuple[torch.Tensor, torch.Tensor] = None
|
||||
|
||||
self.sp_size = get_sequence_parallel_world_size()
|
||||
self.sp_rank = get_sequence_parallel_rank()
|
||||
self.attn = Attention(
|
||||
num_heads=self.num_heads,
|
||||
head_size=self.head_dim,
|
||||
causal=False,
|
||||
softmax_scale=self.scaling,
|
||||
num_kv_heads=self.num_kv_heads,
|
||||
)
|
||||
|
||||
def _save_image_kv_caches(
|
||||
self,
|
||||
key: torch.Tensor,
|
||||
@@ -910,6 +940,63 @@ class ImageKVCacheManager:
|
||||
|
||||
return new_key.contiguous(), new_value.contiguous()
|
||||
|
||||
def _sp_save_prompt_kv_caches(
|
||||
self,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
seq_len: int,
|
||||
shard_image_size: int,
|
||||
) -> None:
|
||||
"""
|
||||
We don't need to cached last token, since it all false and nevel attn to in non-first step.
|
||||
With sp the key len is [text, image_shard], image_shard in rank include [image token, padding token]
|
||||
The cache is [prompt0, prompt1], see _prepare_attention_mask_for_generation and
|
||||
_update_model_kwargs_for_generation
|
||||
"""
|
||||
bs, q_len, num_kv_heads, head_dim = key.shape
|
||||
assert q_len == seq_len, f"for first-step, {q_len} != {seq_len}"
|
||||
key = key.reshape(-1, num_kv_heads, head_dim)
|
||||
value = value.reshape(-1, num_kv_heads, head_dim)
|
||||
cached_prompt_len = seq_len - shard_image_size
|
||||
if bs > 1:
|
||||
assert bs == 2, "for cfg case, bs must be 2"
|
||||
|
||||
cached_key = []
|
||||
cached_value = []
|
||||
for b in range(bs):
|
||||
base = b * seq_len
|
||||
# cache text prompt
|
||||
cached_key.append(key[base : base + cached_prompt_len])
|
||||
cached_value.append(value[base : base + cached_prompt_len])
|
||||
|
||||
cached_key = torch.cat(cached_key, dim=0)
|
||||
cached_value = torch.cat(cached_value, dim=0)
|
||||
self.image_kv_cache_map = (cached_key, cached_value)
|
||||
|
||||
def _sp_get_prompt_kv_caches(
|
||||
self,
|
||||
key: torch.Tensor,
|
||||
seq_len: int,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
cached_key, cached_value = self.image_kv_cache_map
|
||||
bs, q_len, kv_head_num, head_dim = key.shape
|
||||
|
||||
cached_prompt_len = cached_key.shape[0] // bs
|
||||
|
||||
assert cached_prompt_len == seq_len - q_len
|
||||
|
||||
joint_text_key = []
|
||||
joint_text_value = []
|
||||
for b in range(bs):
|
||||
cache_base = b * cached_prompt_len
|
||||
joint_text_key.append(cached_key[cache_base : cache_base + cached_prompt_len])
|
||||
joint_text_value.append(cached_value[cache_base : cache_base + cached_prompt_len])
|
||||
|
||||
joint_text_key = torch.cat(joint_text_key, dim=0).reshape(bs, cached_prompt_len, kv_head_num, head_dim)
|
||||
joint_text_value = torch.cat(joint_text_value, dim=0).reshape(bs, cached_prompt_len, kv_head_num, head_dim)
|
||||
|
||||
return joint_text_key.contiguous(), joint_text_value.contiguous()
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
query: torch.Tensor,
|
||||
@@ -923,6 +1010,7 @@ class ImageKVCacheManager:
|
||||
|
||||
query_lens = kwargs.get("query_lens")
|
||||
seq_lens = kwargs.get("seq_lens")
|
||||
shard_image_size = kwargs.get("shard_image_size")
|
||||
bs = len(query_lens)
|
||||
q_len = query_lens[0]
|
||||
seq_len = seq_lens[0]
|
||||
@@ -936,25 +1024,54 @@ class ImageKVCacheManager:
|
||||
query = query.reshape(bs, q_len, head_num_per_rank, head_dim)
|
||||
key = key.reshape(bs, q_len, kv_head_num_per_rank, head_dim)
|
||||
value = value.reshape(bs, q_len, kv_head_num_per_rank, head_dim)
|
||||
|
||||
if first_step:
|
||||
self.image_kv_cache_map = None
|
||||
self._save_image_kv_caches(key, value, seq_len)
|
||||
if self.sp_size <= 1:
|
||||
self._save_image_kv_caches(key, value, seq_len)
|
||||
else:
|
||||
self._sp_save_prompt_kv_caches(key, value, seq_len, shard_image_size)
|
||||
cached_prompt_len = self.image_kv_cache_map[0].shape[0] // bs
|
||||
# joint text part
|
||||
joint_text_query = query[:, :cached_prompt_len, :, :]
|
||||
joint_text_key = key[:, :cached_prompt_len, :, :]
|
||||
joint_text_value = value[:, :cached_prompt_len, :, :]
|
||||
# image part
|
||||
query = query[:, cached_prompt_len:, :, :]
|
||||
key = key[:, cached_prompt_len:, :, :]
|
||||
value = value[:, cached_prompt_len:, :, :]
|
||||
else:
|
||||
key, value = self._update_image_kv_caches(key, value, seq_len)
|
||||
|
||||
query = query.transpose(1, 2).contiguous()
|
||||
key = key.transpose(1, 2).contiguous()
|
||||
value = value.transpose(1, 2).contiguous()
|
||||
if self.sp_size <= 1:
|
||||
key, value = self._update_image_kv_caches(key, value, seq_len)
|
||||
else:
|
||||
joint_text_query = query[:, :0, :, :]
|
||||
joint_text_key, joint_text_value = self._sp_get_prompt_kv_caches(key, seq_len)
|
||||
|
||||
key = repeat_kv(key, repeat_num)
|
||||
value = repeat_kv(value, repeat_num)
|
||||
if self.sp_size > 1:
|
||||
joint_text_key = repeat_kv(joint_text_key, repeat_num)
|
||||
joint_text_value = repeat_kv(joint_text_value, repeat_num)
|
||||
|
||||
attention_mask = attention_mask.contiguous()
|
||||
|
||||
attn_output = F.scaled_dot_product_attention(query, key, value, attn_mask=attention_mask, dropout_p=0.0)
|
||||
if self.sp_size <= 1:
|
||||
attn_metadata = AttentionMetadata(
|
||||
attn_mask=attention_mask,
|
||||
)
|
||||
else:
|
||||
attn_metadata = AttentionMetadata(
|
||||
joint_query=joint_text_query,
|
||||
joint_key=joint_text_key,
|
||||
joint_value=joint_text_value,
|
||||
joint_strategy="front",
|
||||
attn_mask=attention_mask,
|
||||
)
|
||||
# Compute attention using unified attention layer
|
||||
attn_output = self.attn(query, key, value, attn_metadata)
|
||||
|
||||
attn_output = attn_output.transpose(1, 2).contiguous() # [bs, q_len, heads, head_dim]
|
||||
# attn_output = F.scaled_dot_product_attention(query, key, value, attn_mask=attention_mask, dropout_p=0.0)
|
||||
|
||||
# attn_output = attn_output.transpose(1, 2).contiguous() # [bs, q_len, heads, head_dim]
|
||||
attn_output = attn_output.reshape(bs * q_len, head_num_per_rank, head_dim)
|
||||
return attn_output
|
||||
|
||||
@@ -1512,7 +1629,13 @@ class HunYuanAttention(nn.Module):
|
||||
)
|
||||
|
||||
# default image_token_len = timestamp + 4096*image_tokes
|
||||
self.image_attn = ImageKVCacheManager(image_token_len=4097)
|
||||
self.image_attn = ImageKVCacheManager(
|
||||
num_heads=self.num_heads,
|
||||
head_dim=self.head_dim,
|
||||
num_kv_heads=self.num_kv_heads,
|
||||
scaling=self.scaling,
|
||||
image_token_len=4097,
|
||||
)
|
||||
self.image_rope2d_emb = HunYuanRotary2DEmbedder(
|
||||
num_heads=self.num_heads,
|
||||
num_kv_heads=self.num_kv_heads,
|
||||
@@ -1709,8 +1832,73 @@ class HunyuanImage3PreTrainedModel(PreTrainedModel):
|
||||
module.weight.data[module.padding_idx].zero_()
|
||||
|
||||
|
||||
class HunyuanImagePreprocessor(nn.Module):
|
||||
"""
|
||||
Prepares hidden_states for SP by separating to text, image parts
|
||||
|
||||
Split hidden_states [B, seq_len, D] to
|
||||
- text part: [B, text_seq_len, D]
|
||||
- image part: [B, image_seq_len + last_token, D]
|
||||
"""
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
custom_pos_emb: tuple[torch.FloatTensor],
|
||||
position_ids: torch.LongTensor,
|
||||
image_token_len: int,
|
||||
is_first_step: bool = False,
|
||||
):
|
||||
# ---------- compute prompt length ----------
|
||||
prompt_len = hidden_states.shape[1] - image_token_len - 1 if is_first_step else 0
|
||||
|
||||
# ---------- hidden_states ----------
|
||||
text_hidden_states = hidden_states[:, :prompt_len, :]
|
||||
image_hidden_states = hidden_states[:, prompt_len:, :]
|
||||
|
||||
# ---------- position_ids ----------
|
||||
text_position_ids = position_ids[:, :prompt_len]
|
||||
image_position_ids = position_ids[:, prompt_len:]
|
||||
|
||||
# ---------- custom_pos_emb ----------
|
||||
text_custom_pos_emb = (custom_pos_emb[0][:, :prompt_len, :], custom_pos_emb[1][:, :prompt_len, :])
|
||||
image_custom_pos_emb = (custom_pos_emb[0][:, prompt_len:, :], custom_pos_emb[1][:, prompt_len:, :])
|
||||
|
||||
return (
|
||||
text_hidden_states,
|
||||
image_hidden_states,
|
||||
text_position_ids,
|
||||
image_position_ids,
|
||||
text_custom_pos_emb[0],
|
||||
image_custom_pos_emb[0],
|
||||
text_custom_pos_emb[1],
|
||||
image_custom_pos_emb[1],
|
||||
)
|
||||
|
||||
|
||||
class UnifiledCat(nn.Module):
|
||||
def forward(self, x, y, dim: int = 1):
|
||||
return torch.cat([x, y], dim=dim)
|
||||
|
||||
|
||||
class HunyuanImagePostprocessor(nn.Module):
|
||||
def forward(self, x):
|
||||
return x
|
||||
|
||||
|
||||
class HunyuanImage3Model(nn.Module):
|
||||
def __init__(self, config: HunyuanImage3Config, quant_config: QuantizationConfig | None, prefix: str = ""):
|
||||
_sp_plan = {
|
||||
# Split custom_pos_emb tuple elements (cos, sin) at model forward input
|
||||
"pre_processor": {
|
||||
1: SequenceParallelInput(split_dim=1, expected_dims=3, split_output=True, auto_pad=True),
|
||||
3: SequenceParallelInput(split_dim=1, expected_dims=2, split_output=True, auto_pad=True),
|
||||
5: SequenceParallelInput(split_dim=1, expected_dims=3, split_output=True, auto_pad=True), # sin
|
||||
7: SequenceParallelInput(split_dim=1, expected_dims=3, split_output=True, auto_pad=True), # cos
|
||||
},
|
||||
"post_processor": SequenceParallelOutput(gather_dim=1, expected_dims=3),
|
||||
}
|
||||
|
||||
def __init__(self, config: HunyuanImage3Config, quant_config=None, prefix: str = ""):
|
||||
super().__init__()
|
||||
lora_config = None
|
||||
self.num_redundant_experts = 0
|
||||
@@ -1748,6 +1936,10 @@ class HunyuanImage3Model(nn.Module):
|
||||
else:
|
||||
self.norm = PPMissingLayer()
|
||||
|
||||
self.pre_processor = HunyuanImagePreprocessor()
|
||||
self.unifiled_cat = UnifiledCat()
|
||||
self.post_processor = HunyuanImagePostprocessor()
|
||||
|
||||
def _split_qkv_weight(self, qkv: torch.Tensor):
|
||||
num_attention_heads = self.config.num_attention_heads
|
||||
num_kv_heads = getattr(self.config, "num_key_value_heads", self.config.num_attention_heads)
|
||||
@@ -1998,6 +2190,11 @@ class HunyuanImage3Model(nn.Module):
|
||||
loaded_params.add(name)
|
||||
return loaded_params
|
||||
|
||||
def _split_result(self, x, text_prompt_len):
|
||||
text_output = x[:, 0:text_prompt_len, :].contiguous()
|
||||
image_output = x[:, text_prompt_len:, :].contiguous()
|
||||
return text_output, image_output
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids: torch.LongTensor | None = None,
|
||||
@@ -2035,6 +2232,71 @@ class HunyuanImage3Model(nn.Module):
|
||||
all_hidden_states = () if output_hidden_states else None
|
||||
all_self_attns = () if output_attentions else None
|
||||
next_decoder_cache = None
|
||||
sp_world_size = get_sequence_parallel_world_size()
|
||||
prompt_size = 0
|
||||
shard_image_size = 0
|
||||
shard_padding_size = 0
|
||||
origin_query_len = query_lens[0]
|
||||
if sp_world_size > 1:
|
||||
assert query_lens[0] == hidden_states.shape[1]
|
||||
(
|
||||
text_hidden_states,
|
||||
image_hidden_states,
|
||||
text_position_ids,
|
||||
image_position_ids,
|
||||
text_custom_pos_emb_sin,
|
||||
image_custom_pos_emb_sin,
|
||||
text_custom_pos_emb_cos,
|
||||
image_custom_pos_emb_cos,
|
||||
) = self.pre_processor(
|
||||
hidden_states,
|
||||
custom_pos_emb,
|
||||
position_ids,
|
||||
num_image_tokens,
|
||||
first_step,
|
||||
)
|
||||
assert len(set(query_lens)) == 1 and len(set(seq_lens)) == 1, (
|
||||
"query_lens and seq_lens must be the same for sequence parallel"
|
||||
)
|
||||
prompt_size = text_hidden_states.shape[1]
|
||||
shard_image_size = image_hidden_states.shape[1]
|
||||
|
||||
if first_step: # [image tokens, last token]
|
||||
shard_padding_size = shard_image_size * sp_world_size - (num_image_tokens + 1)
|
||||
else: # [image tokens, last token]
|
||||
shard_padding_size = shard_image_size * sp_world_size - num_image_tokens
|
||||
if first_step:
|
||||
seq_lens = [prompt_size + shard_image_size for _ in seq_lens]
|
||||
else:
|
||||
seq_lens = [x - y for x, y in zip(seq_lens, query_lens)]
|
||||
seq_lens = [seq_len + shard_image_size - 1 for seq_len in seq_lens]
|
||||
query_lens = [prompt_size + shard_image_size for _ in query_lens]
|
||||
|
||||
if prompt_size > 0:
|
||||
hidden_states = self.unifiled_cat(text_hidden_states, image_hidden_states, dim=1)
|
||||
position_ids = self.unifiled_cat(text_position_ids, image_position_ids, dim=1)
|
||||
custom_pos_emb = (
|
||||
self.unifiled_cat(text_custom_pos_emb_sin, image_custom_pos_emb_sin, dim=1),
|
||||
self.unifiled_cat(text_custom_pos_emb_cos, image_custom_pos_emb_cos, dim=1),
|
||||
)
|
||||
else:
|
||||
hidden_states = image_hidden_states
|
||||
position_ids = image_position_ids
|
||||
custom_pos_emb = (image_custom_pos_emb_sin, image_custom_pos_emb_cos)
|
||||
|
||||
if not first_step:
|
||||
assert torch.all(~attention_mask[..., -1]), "The last token should not be attended to"
|
||||
attention_mask = attention_mask[..., :-1] # we attention without last token
|
||||
|
||||
if shard_padding_size > 0:
|
||||
B, H, Q, K = attention_mask.shape
|
||||
pad = shard_padding_size
|
||||
|
||||
q_pad = attention_mask.new_zeros(B, H, pad, K)
|
||||
attention_mask = torch.cat((attention_mask, q_pad), dim=2)
|
||||
|
||||
k_pad = attention_mask.new_zeros(B, H, Q + pad, pad)
|
||||
attention_mask = torch.cat((attention_mask, k_pad), dim=3)
|
||||
|
||||
for layer_idx, decoder_layer in enumerate(self.layers):
|
||||
if output_hidden_states:
|
||||
@@ -2055,6 +2317,8 @@ class HunyuanImage3Model(nn.Module):
|
||||
seq_lens=seq_lens,
|
||||
num_image_tokens=num_image_tokens,
|
||||
gen_timestep_scatter_index=gen_timestep_scatter_index,
|
||||
shard_image_size=shard_image_size,
|
||||
shard_padding_size=shard_padding_size,
|
||||
)
|
||||
|
||||
hidden_states = layer_outputs[0]
|
||||
@@ -2068,6 +2332,13 @@ class HunyuanImage3Model(nn.Module):
|
||||
# add hidden states from the last decoder layer
|
||||
if output_hidden_states:
|
||||
all_hidden_states += (hidden_states,)
|
||||
if sp_world_size > 1:
|
||||
text_output, image_output = self._split_result(hidden_states, prompt_size)
|
||||
hidden_states = self.post_processor(image_output)
|
||||
hidden_states = self.unifiled_cat(text_output, hidden_states, dim=1)
|
||||
# Since the SP system only records the primary padding information from the first stage,
|
||||
# but different stages may introduce different padding sizes, we truncate it to ensure correct output.
|
||||
hidden_states = hidden_states[:, :origin_query_len, :].contiguous()
|
||||
|
||||
next_cache = None
|
||||
if use_cache:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
@@ -14,12 +15,13 @@ from transformers.generation.utils import ALL_CACHE_NAMES, GenerationMixin
|
||||
from transformers.models.siglip2 import Siglip2VisionConfig, Siglip2VisionModel
|
||||
from transformers.utils.generic import ModelOutput
|
||||
from vllm.config.vllm import get_current_vllm_config
|
||||
from vllm.distributed import get_tensor_model_parallel_rank
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader
|
||||
from vllm.transformers_utils.config import get_config
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
from vllm_omni.diffusion.distributed.utils import get_local_device
|
||||
from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineLoader
|
||||
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
|
||||
from .autoencoder import AutoencoderKLConv3D
|
||||
@@ -61,7 +63,16 @@ def to_device(data, device):
|
||||
return data
|
||||
|
||||
|
||||
class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin, DiffusionPipelineProfilerMixin):
|
||||
_PROFILER_TARGETS = [
|
||||
"model.forward",
|
||||
"model.layers[0].forward",
|
||||
"model.layers[0].self_attn.forward",
|
||||
"model.layers[0].mlp.forward",
|
||||
"vae.encode",
|
||||
"vae.decode",
|
||||
]
|
||||
|
||||
def __init__(self, od_config: OmniDiffusionConfig) -> None:
|
||||
self.hf_config = get_config(od_config.model, trust_remote_code=True)
|
||||
super().__init__(self.hf_config)
|
||||
@@ -78,7 +89,13 @@ class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
)
|
||||
]
|
||||
quant_config = od_config.quantization_config
|
||||
os.environ["DIFFUSION_ATTENTION_BACKEND"] = "TORCH_SDPA"
|
||||
logger.info(
|
||||
"Setting attention backend to TORCH_SDPA. "
|
||||
"HunyuanImage3Pipeline only supports TORCH_SDPA to handle mixed causal and full attention."
|
||||
)
|
||||
self.model = HunyuanImage3Model(self.hf_config, quant_config=quant_config)
|
||||
self.transformer = self.model
|
||||
self.vae = AutoencoderKLConv3D.from_config(self.hf_config.vae)
|
||||
self._pipeline = None
|
||||
self._tkwrapper = TokenizerWrapper(od_config.model)
|
||||
@@ -112,6 +129,9 @@ class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
self.lm_head = nn.Linear(self.hf_config.hidden_size, self.hf_config.vocab_size, bias=False)
|
||||
self.vllm_config = get_current_vllm_config()
|
||||
self.post_init()
|
||||
self.setup_diffusion_pipeline_profiler(
|
||||
enable_diffusion_pipeline_profiler=self.od_config.enable_diffusion_pipeline_profiler,
|
||||
)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
skip_prefixes = ["lm_head."] if self.hf_config.tie_word_embeddings else []
|
||||
@@ -129,8 +149,8 @@ class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
"time_embed_2",
|
||||
"final_layer.model",
|
||||
]
|
||||
tp_rank = get_tensor_model_parallel_rank()
|
||||
device_str = f"{self.model.device.type}:{tp_rank}"
|
||||
|
||||
device_str = f"{get_local_device()}"
|
||||
named_modules = dict(self.named_modules())
|
||||
for prefix in non_model_layer_prefixes:
|
||||
mod = named_modules.get(prefix)
|
||||
@@ -712,6 +732,7 @@ class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
updated_model_kwargs = {
|
||||
"mode": mode,
|
||||
"custom_pos_emb": model_kwargs["custom_pos_emb"],
|
||||
"num_image_tokens": model_kwargs["num_image_tokens"],
|
||||
}
|
||||
|
||||
# update past_key_values keeping its naming used in model code
|
||||
@@ -997,4 +1018,6 @@ class HunyuanImage3Pipeline(HunyuanImage3PreTrainedModel, GenerationMixin):
|
||||
guidance_scale=guidance_scale,
|
||||
)
|
||||
outputs = self._generate(**model_inputs, **kwargs)
|
||||
return DiffusionOutput(output=outputs[0])
|
||||
return DiffusionOutput(
|
||||
output=outputs[0], stage_durations=self.stage_durations if hasattr(self, "stage_durations") else None
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
import functools
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from threading import Lock
|
||||
@@ -30,20 +31,31 @@ def profiler(name: str, func: Callable, instance: Any) -> Callable:
|
||||
logger.info(f"[DiffusionPipelineProfiler] {name} took {duration:.6f}s")
|
||||
# record the profiling data: duration of stages
|
||||
with instance._profiler_lock:
|
||||
instance._stage_durations[name] = duration
|
||||
instance._stage_durations[name] = instance._stage_durations.get(name, 0.0) + duration
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _parse_part(part: str) -> tuple[str, int | None]:
|
||||
"""Parse 'att[num]' into ('att', num)."""
|
||||
if m := re.compile(r"(\w+)\[(\d+)\]").fullmatch(part):
|
||||
return m.group(1), int(m.group(2))
|
||||
return part, None
|
||||
|
||||
|
||||
def _get_attribute_by_path(obj: Any, path: str) -> tuple[Any, str]:
|
||||
"""Traverse an object by dotted path and return (parent_obj, attribute_name)."""
|
||||
parts = path.split(".")
|
||||
current = obj
|
||||
|
||||
for part in parts[:-1]:
|
||||
current = getattr(current, part, None)
|
||||
attr, idx = _parse_part(part)
|
||||
|
||||
current = getattr(current, attr, None)
|
||||
if current is None:
|
||||
return None, None
|
||||
if idx is not None:
|
||||
current = current[idx]
|
||||
|
||||
return current, parts[-1]
|
||||
|
||||
|
||||
Reference in new issue
Block a user