[diffusion] kernel: gated residual layernorm scale shift and layernorm scale shift kernel fusion for Qwen-Image, WAN and HunyuanVideo (#14717)
Co-authored-by: AichenF <aichenf@nvidia.com> Co-authored-by: jianyingzhu <joeyzhu@nvidia.com> Co-authored-by: root <root@a4u8g-0120.ipp2a2.colossus.nvidia.com> Co-authored-by: Yihan Chen <yingluosanqian@example.com> Co-authored-by: 陈一涵 <yingluosanqian@gmail.com> Co-authored-by: Xiaoyu Zhang <35585791+BBuf@users.noreply.github.com>
This commit is contained in:
co-authored by
AichenF
jianyingzhu
root
Yihan Chen
陈一涵
Xiaoyu Zhang
parent
669a9bd180
commit
4739f2e8d5
@@ -51,7 +51,7 @@ class RMSNorm(CustomOp):
|
||||
var_hidden_size: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.weight = nn.Parameter(torch.ones(hidden_size))
|
||||
self.weight = nn.Parameter(torch.ones(hidden_size, dtype=dtype))
|
||||
self.variance_epsilon = eps
|
||||
self.hidden_size = hidden_size
|
||||
self.variance_size_override = (
|
||||
@@ -71,6 +71,7 @@ class RMSNorm(CustomOp):
|
||||
residual: Optional[torch.Tensor] = None,
|
||||
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
|
||||
shape = x.shape
|
||||
device = x.device
|
||||
x = x.reshape(-1, shape[-1])
|
||||
if residual is not None:
|
||||
residual_shape = residual.shape
|
||||
@@ -249,56 +250,55 @@ class LayerNorm(CustomOp):
|
||||
class FP32LayerNorm(nn.LayerNorm):
|
||||
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
|
||||
origin_dtype = inputs.dtype
|
||||
device = inputs.device
|
||||
return F.layer_norm(
|
||||
inputs.float(),
|
||||
self.normalized_shape,
|
||||
self.weight.float() if self.weight is not None else None,
|
||||
self.bias.float() if self.bias is not None else None,
|
||||
self.weight.float().to(device=device) if self.weight is not None else None,
|
||||
self.bias.float().to(device=device) if self.bias is not None else None,
|
||||
self.eps,
|
||||
).to(origin_dtype)
|
||||
|
||||
|
||||
class ScaleResidualLayerNormScaleShift(nn.Module):
|
||||
"""
|
||||
Fused operation that combines:
|
||||
1. Gated residual connection
|
||||
2. LayerNorm
|
||||
3. Scale and shift operations
|
||||
################################################################################
|
||||
# Fused norm kernel
|
||||
################################################################################
|
||||
def _ensure_contiguous(tensor: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
|
||||
return tensor.contiguous() if tensor is not None else None
|
||||
|
||||
This reduces memory bandwidth by combining memory-bound operations.
|
||||
|
||||
class _ScaleResidualNormScaleShift(CustomOp):
|
||||
"""
|
||||
Fused kernel that combines:
|
||||
1. residual_out = residual + gate * x
|
||||
2. normed = layernorm(residual_out) or rmsnorm(residual_out)
|
||||
3. out = normed * (1 + scale) + shift
|
||||
compute_dtype is always fp32 for higher precision.
|
||||
"""
|
||||
|
||||
norm_type: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int,
|
||||
norm_type: str = "rms",
|
||||
eps: float = 1e-6,
|
||||
elementwise_affine: bool = False,
|
||||
dtype: torch.dtype = torch.float32,
|
||||
compute_dtype: torch.dtype | None = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
if norm_type == "rms":
|
||||
self.norm = RMSNorm(
|
||||
hidden_size, has_weight=elementwise_affine, eps=eps, dtype=dtype
|
||||
self.eps = eps
|
||||
self.dtype = dtype
|
||||
if self.norm_type == "rms":
|
||||
self.norm = RMSNorm(hidden_size, eps=eps, dtype=dtype)
|
||||
elif self.norm_type == "layer":
|
||||
self.norm = FP32LayerNorm(
|
||||
hidden_size, elementwise_affine=elementwise_affine, eps=eps, dtype=dtype
|
||||
)
|
||||
elif norm_type == "layer":
|
||||
if compute_dtype == torch.float32:
|
||||
self.norm = FP32LayerNorm(
|
||||
hidden_size, elementwise_affine=elementwise_affine, eps=eps
|
||||
)
|
||||
else:
|
||||
self.norm = LayerNorm(
|
||||
hidden_size,
|
||||
elementwise_affine=elementwise_affine,
|
||||
eps=eps,
|
||||
dtype=dtype,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"Norm type {norm_type} not implemented")
|
||||
raise NotImplementedError(f"Norm type {self.norm_type} not implemented")
|
||||
|
||||
def forward(
|
||||
def forward_cuda(
|
||||
self,
|
||||
residual: torch.Tensor,
|
||||
x: torch.Tensor,
|
||||
@@ -306,18 +306,45 @@ class ScaleResidualLayerNormScaleShift(nn.Module):
|
||||
shift: torch.Tensor,
|
||||
scale: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Apply gated residual connection, followed by layernorm and
|
||||
scale/shift in a single fused operation.
|
||||
if x.shape[-1] % 256 != 0 and x.shape[-1] <= 8192:
|
||||
import warnings
|
||||
|
||||
Returns:
|
||||
Tuple containing:
|
||||
- normalized and modulated output of shape: [batch_size, seq_len, inner_dim]
|
||||
- residual value (value after residual connection
|
||||
but before normalization)
|
||||
"""
|
||||
warnings.warn(
|
||||
"FusedScaleResidualNormScaleShift cuda not available, using native fallback",
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.forward_native(residual, x, gate, shift, scale)
|
||||
|
||||
from sglang.jit_kernel.diffusion.cutedsl.scale_residual_norm_scale_shift import (
|
||||
fused_scale_residual_norm_scale_shift,
|
||||
)
|
||||
|
||||
return fused_scale_residual_norm_scale_shift(
|
||||
residual.contiguous(),
|
||||
x.contiguous(),
|
||||
gate.contiguous() if isinstance(gate, torch.Tensor) else None,
|
||||
_ensure_contiguous(getattr(self.norm, "weight", None)),
|
||||
_ensure_contiguous(getattr(self.norm, "bias", None)),
|
||||
scale.contiguous(),
|
||||
shift.contiguous(),
|
||||
self.norm_type,
|
||||
self.eps,
|
||||
)
|
||||
|
||||
def forward_hip(self, *args, **kwargs):
|
||||
# ROCm does not support CUDA/CUTLASS-based fused kernels yet,
|
||||
# so we fall back to the native PyTorch implementation.
|
||||
return self.forward_native(*args, **kwargs)
|
||||
|
||||
def forward_native(
|
||||
self,
|
||||
residual: torch.Tensor,
|
||||
x: torch.Tensor,
|
||||
gate: torch.Tensor | int,
|
||||
shift: torch.Tensor,
|
||||
scale: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
# x.shape: [batch_size, seq_len, inner_dim]
|
||||
# Apply residual connection with gating
|
||||
if isinstance(gate, int):
|
||||
# used by cross-attention, should be 1
|
||||
assert gate == 1
|
||||
@@ -331,91 +358,97 @@ class ScaleResidualLayerNormScaleShift(nn.Module):
|
||||
x.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * gate
|
||||
).flatten(1, 2)
|
||||
else:
|
||||
# used by bidirectional self attention
|
||||
# gate.shape: [batch_size, 1, inner_dim]
|
||||
residual_output = residual + x * gate
|
||||
else:
|
||||
raise ValueError(f"Gate type {type(gate)} not supported")
|
||||
# residual_output.shape: [batch_size, seq_len, inner_dim]
|
||||
|
||||
# Apply normalization
|
||||
normalized = self.norm(residual_output)
|
||||
|
||||
# modulated = fused_scale_shift(
|
||||
# normalized,
|
||||
# scale,
|
||||
# shift,
|
||||
# )
|
||||
modulated = fuse_scale_shift_kernel(
|
||||
normalized,
|
||||
scale,
|
||||
shift,
|
||||
)
|
||||
modulated = fuse_scale_shift_kernel(normalized, scale, shift)
|
||||
return modulated, residual_output
|
||||
|
||||
|
||||
class LayerNormScaleShift(nn.Module):
|
||||
class ScaleResidualLayerNormScaleShift(_ScaleResidualNormScaleShift):
|
||||
norm_type = "layer"
|
||||
|
||||
|
||||
class ScaleResidualRMSNormScaleShift(_ScaleResidualNormScaleShift):
|
||||
norm_type = "rms"
|
||||
|
||||
|
||||
class _NormScaleShift(CustomOp):
|
||||
"""
|
||||
Fused operation that combines LayerNorm with scale and shift operations.
|
||||
This reduces memory bandwidth by combining memory-bound operations.
|
||||
Fused kernel that combines:
|
||||
1. normed = layernorm(x) or rmsnorm(x)
|
||||
2. out = normed * (1 + scale) + shift
|
||||
compute_dtype is always fp32 for higher precision.
|
||||
"""
|
||||
|
||||
norm_type: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int,
|
||||
norm_type: str = "rms",
|
||||
eps: float = 1e-6,
|
||||
elementwise_affine: bool = False,
|
||||
dtype: torch.dtype = torch.float32,
|
||||
compute_dtype: torch.dtype | None = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
self.compute_dtype = compute_dtype
|
||||
if norm_type == "rms":
|
||||
self.norm = RMSNorm(hidden_size, has_weight=elementwise_affine, eps=eps)
|
||||
elif norm_type == "layer":
|
||||
if self.compute_dtype == torch.float32:
|
||||
self.norm = FP32LayerNorm(
|
||||
hidden_size, elementwise_affine=elementwise_affine, eps=eps
|
||||
)
|
||||
else:
|
||||
self.norm = nn.LayerNorm(
|
||||
hidden_size,
|
||||
elementwise_affine=elementwise_affine,
|
||||
eps=eps,
|
||||
dtype=dtype,
|
||||
)
|
||||
self.eps = eps
|
||||
if self.norm_type == "rms":
|
||||
self.norm = RMSNorm(hidden_size, eps=eps, dtype=dtype)
|
||||
elif self.norm_type == "layer":
|
||||
self.norm = FP32LayerNorm(
|
||||
hidden_size, elementwise_affine=elementwise_affine, eps=eps, dtype=dtype
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"Norm type {norm_type} not implemented")
|
||||
raise NotImplementedError(f"Norm type {self.norm_type} not implemented")
|
||||
|
||||
def forward(
|
||||
def forward_cuda(
|
||||
self, x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor
|
||||
) -> torch.Tensor:
|
||||
if x.shape[-1] % 256 != 0 and x.shape[-1] <= 8192:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"FusedNormScaleShift cuda not available, using native fallback",
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.forward_native(x, shift, scale)
|
||||
|
||||
from sglang.jit_kernel.diffusion.cutedsl.scale_residual_norm_scale_shift import (
|
||||
fused_norm_scale_shift,
|
||||
)
|
||||
|
||||
return fused_norm_scale_shift(
|
||||
x.contiguous(),
|
||||
_ensure_contiguous(getattr(self.norm, "weight", None)),
|
||||
_ensure_contiguous(getattr(self.norm, "bias", None)),
|
||||
scale.contiguous(),
|
||||
shift.contiguous(),
|
||||
self.norm_type,
|
||||
self.eps,
|
||||
)
|
||||
|
||||
def forward_hip(self, *args, **kwargs):
|
||||
# ROCm does not support CUDA/CUTLASS-based fused kernels yet,
|
||||
# so we fall back to the native PyTorch implementation.
|
||||
return self.forward_native(*args, **kwargs)
|
||||
|
||||
def forward_native(
|
||||
self, x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor
|
||||
) -> torch.Tensor:
|
||||
"""Apply ln followed by scale and shift in a single fused operation."""
|
||||
# x.shape: [batch_size, seq_len, inner_dim]
|
||||
normalized = self.norm(x)
|
||||
if self.compute_dtype == torch.float32:
|
||||
normalized = normalized.float()
|
||||
modulated = fuse_scale_shift_kernel(normalized, scale, shift)
|
||||
return modulated.to(x.dtype)
|
||||
|
||||
if scale.dim() == 4:
|
||||
# scale.shape: [batch_size, num_frames, 1, inner_dim]
|
||||
num_frames = scale.shape[1]
|
||||
frame_seqlen = normalized.shape[1] // num_frames
|
||||
output = (
|
||||
normalized.unflatten(dim=1, sizes=(num_frames, frame_seqlen))
|
||||
* (1.0 + scale)
|
||||
+ shift
|
||||
).flatten(1, 2)
|
||||
else:
|
||||
# scale.shape: [batch_size, 1, inner_dim]
|
||||
# shift.shape: [batch_size, 1, inner_dim]
|
||||
output = normalized * (1.0 + scale) + shift
|
||||
|
||||
if self.compute_dtype == torch.float32:
|
||||
output = output.to(x.dtype)
|
||||
class LayerNormScaleShift(_NormScaleShift):
|
||||
norm_type = "layer"
|
||||
|
||||
return output
|
||||
|
||||
class RMSNormScaleShift(_NormScaleShift):
|
||||
norm_type = "rms"
|
||||
|
||||
|
||||
def apply_qk_norm(
|
||||
@@ -470,3 +503,14 @@ def tensor_parallel_rms_norm(x: torch.Tensor, norm: "RMSNorm") -> torch.Tensor:
|
||||
)
|
||||
output = x_fp32 * torch.rsqrt(variance + norm.variance_epsilon) * weight
|
||||
return output.to(dtype=src_dtype)
|
||||
|
||||
|
||||
# TODO: Workaround, fuse norm with new select01 kernel
|
||||
def apply_layernorm_only(x: torch.Tensor, layernorm_scale_shift: LayerNormScaleShift):
|
||||
return norm_infer(
|
||||
x.view(-1, x.shape[-1]),
|
||||
layernorm_scale_shift.norm.weight,
|
||||
layernorm_scale_shift.norm.bias,
|
||||
eps=layernorm_scale_shift.eps,
|
||||
is_rms_norm=False,
|
||||
).view(x.shape)
|
||||
|
||||
@@ -296,24 +296,14 @@ class CausalWanTransformerBlock(nn.Module):
|
||||
raise Exception
|
||||
assert cross_attn_norm is True
|
||||
self.self_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=True,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
dim, eps=eps, elementwise_affine=True, dtype=torch.float32
|
||||
)
|
||||
|
||||
# 2. Cross-attention
|
||||
# Only T2V for now
|
||||
self.attn2 = WanT2VCrossAttention(dim, num_heads, qk_norm=qk_norm, eps=eps)
|
||||
self.cross_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
dim, eps=eps, elementwise_affine=False, dtype=torch.float32
|
||||
)
|
||||
|
||||
# 3. Feed-forward
|
||||
@@ -484,11 +474,9 @@ class CausalWanTransformer3DModel(BaseDiT, OffloadableDiTMixin):
|
||||
# 4. Output norm & projection
|
||||
self.norm_out = LayerNormScaleShift(
|
||||
inner_dim,
|
||||
norm_type="layer",
|
||||
eps=config.eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
self.proj_out = nn.Linear(
|
||||
inner_dim, config.out_channels * math.prod(config.patch_size)
|
||||
|
||||
@@ -76,10 +76,10 @@ class MMDoubleStreamBlock(nn.Module):
|
||||
|
||||
# Fused operations for image stream
|
||||
self.img_attn_norm = LayerNormScaleShift(
|
||||
hidden_size, norm_type="layer", elementwise_affine=False, dtype=dtype
|
||||
hidden_size, elementwise_affine=False, dtype=dtype
|
||||
)
|
||||
self.img_attn_residual_mlp_norm = ScaleResidualLayerNormScaleShift(
|
||||
hidden_size, norm_type="layer", elementwise_affine=False, dtype=dtype
|
||||
hidden_size, elementwise_affine=False, dtype=dtype
|
||||
)
|
||||
self.img_mlp_residual = MulAdd()
|
||||
|
||||
@@ -122,10 +122,10 @@ class MMDoubleStreamBlock(nn.Module):
|
||||
|
||||
# Fused operations for text stream
|
||||
self.txt_attn_norm = LayerNormScaleShift(
|
||||
hidden_size, norm_type="layer", elementwise_affine=False, dtype=dtype
|
||||
hidden_size, elementwise_affine=False, dtype=dtype
|
||||
)
|
||||
self.txt_attn_residual_mlp_norm = ScaleResidualLayerNormScaleShift(
|
||||
hidden_size, norm_type="layer", elementwise_affine=False, dtype=dtype
|
||||
hidden_size, elementwise_affine=False, dtype=dtype
|
||||
)
|
||||
self.txt_mlp_residual = MulAdd()
|
||||
|
||||
@@ -299,7 +299,6 @@ class MMSingleStreamBlock(nn.Module):
|
||||
# Fused operations with better naming
|
||||
self.input_norm_scale_shift = LayerNormScaleShift(
|
||||
hidden_size,
|
||||
norm_type="layer",
|
||||
eps=1e-6,
|
||||
elementwise_affine=False,
|
||||
dtype=dtype,
|
||||
|
||||
@@ -19,8 +19,10 @@ from sglang.multimodal_gen.runtime.distributed import get_local_torch_device
|
||||
from sglang.multimodal_gen.runtime.layers.attention import USPAttention
|
||||
from sglang.multimodal_gen.runtime.layers.elementwise import MulAdd
|
||||
from sglang.multimodal_gen.runtime.layers.layernorm import (
|
||||
LayerNorm,
|
||||
LayerNormScaleShift,
|
||||
RMSNorm,
|
||||
ScaleResidualLayerNormScaleShift,
|
||||
apply_layernorm_only,
|
||||
apply_qk_norm,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.linear import ReplicatedLinear
|
||||
@@ -646,7 +648,9 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
dim, 6 * dim, bias=True
|
||||
), # For scale, shift, gate for norm1 and norm2
|
||||
)
|
||||
self.img_norm1 = LayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
self.img_norm1 = LayerNormScaleShift(
|
||||
hidden_size=dim, eps=eps, elementwise_affine=False
|
||||
)
|
||||
|
||||
self.attn = QwenImageCrossAttention(
|
||||
dim=dim,
|
||||
@@ -655,7 +659,9 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
context_pre_only=False,
|
||||
head_dim=attention_head_dim,
|
||||
)
|
||||
self.img_norm2 = LayerNorm(dim, eps=eps, elementwise_affine=False)
|
||||
self.img_norm2 = ScaleResidualLayerNormScaleShift(
|
||||
hidden_size=dim, eps=eps, elementwise_affine=False
|
||||
)
|
||||
self.img_mlp = FeedForward(
|
||||
dim=dim, dim_out=dim, activation_fn="gelu-approximate"
|
||||
)
|
||||
@@ -667,16 +673,37 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
dim, 6 * dim, bias=True
|
||||
), # For scale, shift, gate for norm1 and norm2
|
||||
)
|
||||
self.txt_norm1 = LayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
self.txt_norm1 = LayerNormScaleShift(
|
||||
hidden_size=dim, eps=eps, elementwise_affine=False
|
||||
)
|
||||
# Text doesn't need separate attention - it's handled by img_attn joint computation
|
||||
self.txt_norm2 = LayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
self.txt_norm2 = ScaleResidualLayerNormScaleShift(
|
||||
hidden_size=dim, eps=eps, elementwise_affine=False
|
||||
)
|
||||
self.txt_mlp = FeedForward(
|
||||
dim=dim, dim_out=dim, activation_fn="gelu-approximate"
|
||||
)
|
||||
# Utils
|
||||
self.fuse_mul_add = MulAdd()
|
||||
|
||||
def _modulate(self, x, mod_params, index=None):
|
||||
def _modulate(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
mod_params: torch.Tensor,
|
||||
norm_module: Union[LayerNormScaleShift, ScaleResidualLayerNormScaleShift],
|
||||
index: Optional[torch.Tensor] = None,
|
||||
gate_x: Optional[torch.Tensor] = None,
|
||||
residual_x: Optional[torch.Tensor] = None,
|
||||
) -> Union[
|
||||
Tuple[torch.Tensor, torch.Tensor],
|
||||
Tuple[torch.Tensor, torch.Tensor, torch.Tensor],
|
||||
]:
|
||||
# Apply attention gates and add residual (like in Megatron)
|
||||
# - residual_out = gate_x * x + residual_x
|
||||
# - x = norm(residual_out) * (1 + scale) + shift
|
||||
# TODO: clean code here
|
||||
is_scale_residual = isinstance(norm_module, ScaleResidualLayerNormScaleShift)
|
||||
|
||||
shift, scale, gate = mod_params.chunk(3, dim=-1)
|
||||
if index is not None:
|
||||
actual_batch = x.shape[0]
|
||||
@@ -689,12 +716,16 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
scale[actual_batch : 2 * actual_batch],
|
||||
)
|
||||
gate0, gate1 = gate[:actual_batch], gate[actual_batch : 2 * actual_batch]
|
||||
|
||||
if _is_cuda:
|
||||
if is_scale_residual:
|
||||
x = gate_x * x + residual_x
|
||||
residual_out = x
|
||||
if not x.is_contiguous():
|
||||
x = x.contiguous()
|
||||
if not index.is_contiguous():
|
||||
index = index.contiguous()
|
||||
# TODO: fuse norm with above select01 kernel, workaround now
|
||||
x = apply_layernorm_only(x, norm_module)
|
||||
x, gate_result = fuse_scale_shift_gate_select01_kernel(
|
||||
x,
|
||||
scale0=scale0.contiguous(),
|
||||
@@ -705,7 +736,10 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
gate1=gate1.contiguous(),
|
||||
index=index,
|
||||
)
|
||||
return x, gate_result
|
||||
if is_scale_residual:
|
||||
return x, residual_out, gate_result
|
||||
else:
|
||||
return x, gate_result
|
||||
else:
|
||||
mask = (index == 0).unsqueeze(-1)
|
||||
shift_result = torch.where(
|
||||
@@ -715,15 +749,34 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
mask, scale0.unsqueeze(1), scale1.unsqueeze(1)
|
||||
)
|
||||
gate_result = torch.where(mask, gate0.unsqueeze(1), gate1.unsqueeze(1))
|
||||
return (
|
||||
self.fuse_mul_add(x, scale_result, shift_result, k=1.0),
|
||||
gate_result,
|
||||
)
|
||||
if is_scale_residual:
|
||||
modulated, residual_out = norm_module(
|
||||
residual=residual_x,
|
||||
x=x,
|
||||
gate=gate_x,
|
||||
shift=shift_result,
|
||||
scale=scale_result,
|
||||
)
|
||||
return modulated, residual_out, gate_result
|
||||
else:
|
||||
modulated = norm_module(x=x, shift=shift_result, scale=scale_result)
|
||||
return modulated, gate_result
|
||||
else:
|
||||
shift_result = shift.unsqueeze(1)
|
||||
scale_result = scale.unsqueeze(1)
|
||||
gate_result = gate.unsqueeze(1)
|
||||
return self.fuse_mul_add(x, scale_result, shift_result, k=1.0), gate_result
|
||||
if is_scale_residual:
|
||||
modulated, residual_out = norm_module(
|
||||
residual=residual_x,
|
||||
x=x,
|
||||
gate=gate_x,
|
||||
shift=shift_result,
|
||||
scale=scale_result,
|
||||
)
|
||||
return modulated, residual_out, gate_result
|
||||
else:
|
||||
modulated = norm_module(x=x, shift=shift_result, scale=scale_result)
|
||||
return modulated, gate_result
|
||||
|
||||
def forward(
|
||||
self,
|
||||
@@ -745,13 +798,15 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
txt_mod1, txt_mod2 = txt_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
|
||||
|
||||
# Process image stream - norm1 + modulation
|
||||
|
||||
img_normed = self.img_norm1(hidden_states)
|
||||
|
||||
img_modulated, img_gate1 = self._modulate(img_normed, img_mod1, modulate_index)
|
||||
img_modulated, img_gate1 = self._modulate(
|
||||
hidden_states, img_mod1, self.img_norm1, modulate_index
|
||||
)
|
||||
# Process text stream - norm1 + modulation
|
||||
txt_normed = self.txt_norm1(encoder_hidden_states)
|
||||
txt_modulated, txt_gate1 = self._modulate(txt_normed, txt_mod1)
|
||||
txt_shift1, txt_scale1, txt_gate1_raw = txt_mod1.chunk(3, dim=-1)
|
||||
txt_modulated = self.txt_norm1(
|
||||
encoder_hidden_states, shift=txt_shift1, scale=txt_scale1
|
||||
)
|
||||
txt_gate1 = txt_gate1_raw.unsqueeze(1)
|
||||
|
||||
# Use QwenAttnProcessor2_0 for joint attention computation
|
||||
# This directly implements the DoubleStreamLayerMegatron logic:
|
||||
@@ -772,23 +827,28 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
|
||||
# QwenAttnProcessor2_0 returns (img_output, txt_output) when encoder_hidden_states is provided
|
||||
img_attn_output, txt_attn_output = attn_output
|
||||
|
||||
# Apply attention gates and add residual (like in Megatron)
|
||||
hidden_states = hidden_states + img_gate1 * img_attn_output
|
||||
|
||||
encoder_hidden_states = encoder_hidden_states + txt_gate1 * txt_attn_output
|
||||
|
||||
# Process image stream - norm2 + MLP
|
||||
img_normed2 = self.img_norm2(hidden_states)
|
||||
img_modulated2, img_gate2 = self._modulate(
|
||||
img_normed2, img_mod2, modulate_index
|
||||
img_modulated2, hidden_states, img_gate2 = self._modulate(
|
||||
img_attn_output,
|
||||
img_mod2,
|
||||
self.img_norm2,
|
||||
modulate_index,
|
||||
gate_x=img_gate1,
|
||||
residual_x=hidden_states,
|
||||
)
|
||||
img_mlp_output = self.img_mlp(img_modulated2)
|
||||
hidden_states = self.fuse_mul_add(img_mlp_output, img_gate2, hidden_states)
|
||||
|
||||
# Process text stream - norm2 + MLP
|
||||
txt_normed2 = self.txt_norm2(encoder_hidden_states)
|
||||
txt_modulated2, txt_gate2 = self._modulate(txt_normed2, txt_mod2)
|
||||
txt_shift2, txt_scale2, txt_gate2_raw = txt_mod2.chunk(3, dim=-1)
|
||||
txt_modulated2, encoder_hidden_states = self.txt_norm2(
|
||||
residual=encoder_hidden_states,
|
||||
x=txt_attn_output,
|
||||
gate=txt_gate1,
|
||||
shift=txt_shift2,
|
||||
scale=txt_scale2,
|
||||
)
|
||||
txt_gate2 = txt_gate2_raw.unsqueeze(1)
|
||||
txt_mlp_output = self.txt_mlp(txt_modulated2)
|
||||
encoder_hidden_states = self.fuse_mul_add(
|
||||
txt_mlp_output, txt_gate2, encoder_hidden_states
|
||||
|
||||
@@ -298,7 +298,12 @@ class WanTransformerBlock(nn.Module):
|
||||
super().__init__()
|
||||
|
||||
# 1. Self-attention
|
||||
self.norm1 = FP32LayerNorm(dim, eps, elementwise_affine=False)
|
||||
self.norm1 = LayerNormScaleShift(
|
||||
dim,
|
||||
eps=eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
)
|
||||
self.to_q = ColumnParallelLinear(dim, dim, bias=True, gather_output=False)
|
||||
self.to_k = ColumnParallelLinear(dim, dim, bias=True, gather_output=False)
|
||||
self.to_v = ColumnParallelLinear(dim, dim, bias=True, gather_output=False)
|
||||
@@ -344,11 +349,9 @@ class WanTransformerBlock(nn.Module):
|
||||
self.tp_rmsnorm = qk_norm == "rms_norm_across_heads" and tp_size > 1
|
||||
self.self_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=True,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
|
||||
# 2. Cross-attention
|
||||
@@ -372,11 +375,9 @@ class WanTransformerBlock(nn.Module):
|
||||
)
|
||||
self.cross_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
|
||||
# 3. Feed-forward
|
||||
@@ -418,8 +419,7 @@ class WanTransformerBlock(nn.Module):
|
||||
assert shift_msa.dtype == torch.float32
|
||||
|
||||
# 1. Self-attention
|
||||
norm1 = self.norm1(hidden_states.float())
|
||||
norm_hidden_states = (norm1 * (1 + scale_msa) + shift_msa).to(orig_dtype)
|
||||
norm_hidden_states = self.norm1(hidden_states, shift_msa, scale_msa)
|
||||
query, _ = self.to_q(norm_hidden_states)
|
||||
key, _ = self.to_k(norm_hidden_states)
|
||||
value, _ = self.to_v(norm_hidden_states)
|
||||
@@ -506,7 +506,12 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
super().__init__()
|
||||
|
||||
# 1. Self-attention
|
||||
self.norm1 = FP32LayerNorm(dim, eps, elementwise_affine=False)
|
||||
self.norm1 = LayerNormScaleShift(
|
||||
dim,
|
||||
eps=eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
)
|
||||
self.to_q = ColumnParallelLinear(dim, dim, bias=True, gather_output=True)
|
||||
self.to_k = ColumnParallelLinear(dim, dim, bias=True, gather_output=True)
|
||||
self.to_v = ColumnParallelLinear(dim, dim, bias=True, gather_output=True)
|
||||
@@ -538,11 +543,9 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
assert cross_attn_norm is True
|
||||
self.self_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=True,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
|
||||
if AttentionBackendEnum.VIDEO_SPARSE_ATTN in supported_attention_backends:
|
||||
@@ -568,11 +571,9 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
)
|
||||
self.cross_attn_residual_norm = ScaleResidualLayerNormScaleShift(
|
||||
dim,
|
||||
norm_type="layer",
|
||||
eps=eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
|
||||
# 3. Feed-forward
|
||||
@@ -600,9 +601,7 @@ class WanTransformerBlock_VSA(nn.Module):
|
||||
assert shift_msa.dtype == torch.float32
|
||||
|
||||
# 1. Self-attention
|
||||
norm_hidden_states = (
|
||||
self.norm1(hidden_states.float()) * (1 + scale_msa) + shift_msa
|
||||
).to(orig_dtype)
|
||||
norm_hidden_states = self.norm1(hidden_states, shift_msa, scale_msa)
|
||||
query, _ = self.to_q(norm_hidden_states)
|
||||
key, _ = self.to_k(norm_hidden_states)
|
||||
value, _ = self.to_v(norm_hidden_states)
|
||||
@@ -736,11 +735,9 @@ class WanTransformer3DModel(CachableDiT, OffloadableDiTMixin):
|
||||
# 4. Output norm & projection
|
||||
self.norm_out = LayerNormScaleShift(
|
||||
inner_dim,
|
||||
norm_type="layer",
|
||||
eps=config.eps,
|
||||
elementwise_affine=False,
|
||||
dtype=torch.float32,
|
||||
compute_dtype=torch.float32,
|
||||
)
|
||||
self.proj_out = nn.Linear(
|
||||
inner_dim, config.out_channels * math.prod(config.patch_size)
|
||||
|
||||
Reference in New Issue
Block a user