Files
apache--tvm/python/tvm/relax/frontend/nn/modules.py
T
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

1200 lines
34 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=too-many-arguments,invalid-name,protected-access,unused-argument
# ruff: noqa: RUF005
"""Builtin Modules."""
from collections.abc import Sequence
from tvm import relax as rx
from tvm import tirx
from . import op
from .core import Effect, Module, ModuleList, Parameter, Tensor, get_default_dtype
class IOEffect(Effect):
"""
Modeling IO side effect, for example, printing the content of Tensors on screen, inserting
debug breakpoints, etc.
"""
effect: rx.Var | None
def __init__(self):
self.effect = None
def emit_init(self, name_hint, builder: rx.BlockBuilder) -> list[rx.DataflowVar]:
return [builder.emit(rx.op.null_value(), f"{name_hint}.io")]
def create(self, name_hint: str) -> list[rx.Var]:
assert self.effect is None
effect = rx.Var(f"{name_hint}.io", ty=rx.AnyType())
return [effect]
def set_state(self, state_vars: list[rx.Var]) -> None:
(self.effect,) = state_vars
def finalize(self) -> list[rx.Var]:
result = self.effect
self.effect = None
return [result]
class ReLU(Module):
"""Module for ReLU activation layer."""
def forward(self, x: Tensor):
return op.relu(x)
class SiLU(Module):
"""Module for SiLU activation layer."""
def forward(self, x: Tensor):
return op.silu(x)
class GELU(Module):
"""Module for GELU activation layer."""
def forward(self, x: Tensor):
return op.gelu(x)
class Identity(Module):
"""Module that does nothing, sometimes useful for naming purposes."""
def forward(self, x: Tensor):
"""Forward method for identity.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
Result : Tensor
The unchanged input tensor.
"""
return x
class Linear(Module):
"""Applies a linear transformation :math:`y = xW^T + b`.
Parameters
----------
in_features : Union[int, str, tirx.Expr]
Size of each input sample. Can be symbolic.
out_features : Union[int, str, tirx.Expr]
Size of each output sample. Can be symbolic.
bias : bool
If ``True``, adds a learnable bias. Default: ``True``.
dtype : Optional[str]
Data type for weight (and bias when *out_dtype* is ``None``).
``None`` uses the default dtype.
out_dtype : Optional[str]
If set, the matmul accumulates in this dtype and the bias is stored in this dtype
instead of *dtype*. Useful for mixed-precision (e.g. ``float32`` accumulation with
``float16`` weights).
"""
def __init__(
self,
in_features: int | str | tirx.Expr,
out_features: int | str | tirx.Expr,
bias: bool = True,
dtype: str | None = None,
out_dtype: str | None = None,
):
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.out_dtype = out_dtype
self.weight = Parameter((out_features, in_features), dtype)
if bias:
self.bias = Parameter((out_features,), dtype=dtype if out_dtype is None else out_dtype)
else:
self.bias = None
def forward(self, x: Tensor) -> Tensor:
"""
Forward method for linear layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the linear layer.
"""
# x: [*B, in_features]
# w: [in_features, out_features]
w = op.permute_dims(self.weight)
# x: [*B, out_features]
x = op.matmul(x, w, out_dtype=self.out_dtype)
if self.bias is not None:
x = x + self.bias
return x
def to(self, dtype: str | None = None) -> None:
"""
Override to() such that we do not convert bias if there is `out_dtype`.
Otherwise, we might run into dtype mismatch when computing `x + self.bias`
since x is of type `out_dtype` and bias becomes `dtype`, potentially different.
"""
self.weight.to(dtype=dtype)
if self.bias is not None and self.out_dtype is None:
self.bias.to(dtype=dtype)
if dtype is not None and isinstance(getattr(self, "dtype", None), str):
self.dtype = dtype # pylint: disable=attribute-defined-outside-init
class Conv1D(Module):
"""Applies a 1D convolution over an input signal.
Parameters
----------
in_channels : int
Number of channels in the input.
out_channels : int
Number of channels produced by the convolution.
kernel_size : int
Size of the convolving kernel.
stride : int
Stride of the convolution. Default: 1.
padding : int
Zero-padding added to both sides of the input. Default: 0.
dilation : int
Spacing between kernel elements. Default: 1.
groups : int
Number of blocked connections from input to output channels. Default: 1.
bias : bool
If ``True``, adds a learnable bias. Default: ``True``.
dtype : Optional[str]
Data type for weight and bias. ``None`` uses the default dtype.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
kernel_size: int,
stride: int = 1,
padding: int = 0,
dilation: int = 1,
groups: int = 1,
bias: bool = True,
dtype: str | None = None,
) -> None:
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.weight = Parameter(
(
self.out_channels,
int(self.in_channels // self.groups),
self.kernel_size,
),
dtype,
)
if bias:
self.bias = Parameter((self.out_channels,), dtype)
else:
self.bias = None
def forward(self, x: Tensor) -> Tensor:
"""
Forward method for conv1d layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the conv1d layer.
"""
return op.conv1d(
x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups
)
class Conv2D(Module):
"""Applies a 2D convolution over an input image.
Parameters
----------
in_channels : int
Number of channels in the input image.
out_channels : int
Number of channels produced by the convolution.
kernel_size : Union[List[int], int]
Size of the convolving kernel. An int is expanded to a 2-element list.
stride : int
Stride of the convolution. Default: 1.
padding : int
Zero-padding added to both sides of the input. Default: 0.
dilation : int
Spacing between kernel elements. Default: 1.
groups : int
Number of blocked connections from input to output channels. Default: 1.
bias : bool
If ``True``, adds a learnable bias. Default: ``True``.
dtype : Optional[str]
Data type for weight and bias. ``None`` uses the default dtype.
data_layout : str
Layout of the input data, e.g. ``"NCHW"`` or ``"NHWC"``. Default: ``"NCHW"``.
"""
def __init__( # pylint: disable=too-many-arguments
self,
in_channels: int,
out_channels: int,
kernel_size: list[int] | int,
stride: int = 1,
padding: int = 0,
dilation: int = 1,
groups: int = 1,
bias: bool = True,
dtype: str | None = None,
data_layout: str = "NCHW",
):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.data_layout = data_layout
# Allow dynamic input channels.
if isinstance(self.in_channels, int):
in_channels = int(self.in_channels / self.groups)
else:
in_channels = tirx.floordiv(self.in_channels, self.groups)
# Expand kernel size if provided an integer.
if isinstance(kernel_size, int):
self.kernel_size = [kernel_size] * 2
else:
self.kernel_size = kernel_size
kernel_shape = [self.out_channels, in_channels] + list(self.kernel_size)
self.weight = Parameter(kernel_shape, dtype)
if bias:
self.bias = Parameter((self.out_channels,), dtype)
else:
self.bias = None
def forward(self, x: Tensor) -> Tensor: # pylint: disable=invalid-name
"""
Forward method for conv2d layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the conv2d layer.
"""
return op.conv2d(
x,
self.weight,
self.bias,
self.stride,
self.padding,
self.dilation,
self.groups,
self.data_layout,
)
class Conv3D(Module):
"""Applies a 3D convolution over an input volume.
Parameters
----------
in_channels : int
Number of channels in the input volume.
out_channels : int
Number of channels produced by the convolution.
kernel_size : Union[List[int], int]
Size of the convolving kernel. An int is expanded to a 3-element list.
stride : Union[List[int], int]
Stride of the convolution. Default: 1.
padding : Union[List[int], int]
Zero-padding added to each side of the input. Default: 0.
dilation : int
Spacing between kernel elements. Default: 1.
groups : int
Number of blocked connections from input to output channels. Default: 1.
bias : bool
If ``True``, adds a learnable bias. Default: ``True``.
dtype : Optional[str]
Data type for weight and bias. ``None`` uses the default dtype.
data_layout : str
Layout of the input data, e.g. ``"NCDHW"``. Default: ``"NCDHW"``.
"""
def __init__( # pylint: disable=too-many-arguments
self,
in_channels: int,
out_channels: int,
kernel_size: list[int] | int,
stride: list[int] | int = 1,
padding: list[int] | int = 0,
dilation: int = 1,
groups: int = 1,
bias: bool = True,
dtype: str | None = None,
data_layout: str = "NCDHW",
):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.data_layout = data_layout
# Allow dynamic input channels.
if isinstance(self.in_channels, int):
in_channels = int(self.in_channels / self.groups)
else:
in_channels = tirx.floordiv(self.in_channels, self.groups)
# Expand kernel size if given an integer.
if isinstance(kernel_size, int):
self.kernel_size = [kernel_size] * 3
else:
self.kernel_size = kernel_size
kernel_shape = [self.out_channels, self.in_channels] + list(self.kernel_size)
self.weight = Parameter(kernel_shape, dtype)
if bias:
self.bias = Parameter((self.out_channels,), dtype)
else:
self.bias = None
def forward(self, x: Tensor) -> Tensor: # pylint: disable=invalid-name
"""
Forward method for conv3d layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the conv3d layer.
"""
return op.conv3d(
x,
self.weight,
self.bias,
self.stride,
self.padding,
self.dilation,
self.groups,
self.data_layout,
)
class ConvTranspose1D(Module):
"""Applies a 1D transposed convolution (fractionally-strided convolution).
Parameters
----------
in_channels : int
Number of channels in the input.
out_channels : int
Number of channels produced by the transposed convolution.
kernel_size : int
Size of the convolving kernel.
stride : int
Stride of the convolution. Default: 1.
padding : int
Zero-padding added to both sides of the input. Default: 0.
output_padding : int
Additional size added to one side of the output shape. Default: 0.
dilation : int
Spacing between kernel elements. Default: 1.
groups : int
Number of blocked connections from input to output channels. Default: 1.
bias : bool
If ``True``, adds a learnable bias. Default: ``True``.
dtype : Optional[str]
Data type for weight and bias. ``None`` uses the default dtype.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
kernel_size: int,
stride: int = 1,
padding: int = 0,
output_padding: int = 0,
dilation: int = 1,
groups: int = 1,
bias: bool = True,
dtype: str | None = None,
) -> None:
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.output_padding = output_padding
self.dilation = dilation
self.groups = groups
self.weight = Parameter(
(
self.in_channels,
int(self.out_channels // self.groups),
self.kernel_size,
),
dtype,
)
if bias:
self.bias = Parameter((self.out_channels,), dtype)
else:
self.bias = None
def forward(self, x: Tensor) -> Tensor:
"""
Forward method for conv transpose 1d layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the conv transpose 1d layer.
"""
return op.conv1d_transpose(
x,
self.weight,
self.bias,
self.stride,
self.padding,
self.output_padding,
self.dilation,
self.groups,
)
class LayerNorm(Module):
"""Applies Layer Normalization over the last dimension.
Parameters
----------
normalized_shape : int
Size of the last dimension to normalize over.
eps : Optional[float]
Value added to the denominator for numerical stability. Default: ``1e-5``.
elementwise_affine : bool
If ``True``, learnable affine parameters (weight and bias) are added.
Default: ``True``.
dtype : Optional[str]
Data type for the affine parameters. ``None`` uses the default dtype.
"""
def __init__(
self,
normalized_shape: int,
eps: float | None = 1e-5,
elementwise_affine: bool = True,
dtype: str | None = None,
) -> None:
super().__init__()
self.normalized_shape = normalized_shape
self.eps = eps
self.elementwise_affine = elementwise_affine
if self.elementwise_affine:
self.weight = Parameter((normalized_shape,), dtype=dtype)
self.bias = Parameter((normalized_shape,), dtype=dtype)
else:
self.weight = None
self.bias = None
def forward(self, x: Tensor) -> Tensor:
"""
Forward method for layer normalization layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the layer normalization layer.
"""
return op.layer_norm(
x,
normalized_shape=self.normalized_shape,
weight=self.weight,
bias=self.bias,
eps=self.eps,
)
class RMSNorm(Module):
"""Applies Root Mean Square Layer Normalization.
Parameters
----------
hidden_size : int
Size of the weight parameter.
axes : Union[int, List[int]]
The axes over which to compute the RMS norm.
epsilon : float
Value added to the denominator for numerical stability. Default: ``1e-5``.
bias : bool
If ``True``, adds a learnable bias after normalization. Default: ``True``.
dtype : Optional[str]
Data type for the parameters. ``None`` uses the default dtype.
"""
def __init__(
self,
hidden_size: int,
axes: int | list[int],
epsilon: float = 1e-5,
bias: bool = True,
dtype: str | None = None,
):
super().__init__()
self.epsilon = epsilon
self.axes = axes
self.weight = Parameter((hidden_size,), dtype=dtype)
if bias:
self.bias = Parameter((hidden_size,), dtype=dtype)
else:
self.bias = None
def forward(self, x: Tensor):
"""
Forward method for rms norm layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the rms norm layer.
"""
out = op.rms_norm(x, weight=self.weight, axes=self.axes, epsilon=self.epsilon)
if self.bias:
out = op.add(out, self.bias)
return out
class GroupNorm(Module):
"""Applies Group Normalization.
Parameters
----------
num_groups : int
Number of groups to separate the channels into.
num_channels : int
Number of channels in the input, must be divisible by *num_groups*.
eps : float
Value added to the denominator for numerical stability. Default: ``1e-5``.
affine : bool
If ``True``, learnable per-channel affine parameters are added. Default: ``True``.
dtype : Optional[str]
Data type for the affine parameters. ``None`` uses the default dtype.
"""
def __init__(
self,
num_groups: int,
num_channels: int,
eps: float = 1e-5,
affine: bool = True,
dtype: str | None = None,
):
super().__init__()
self.num_groups = num_groups
self.num_channels = num_channels
self.eps = eps
if affine:
self.weight = Parameter((num_channels,), dtype=dtype)
self.bias = Parameter((num_channels,), dtype=dtype)
else:
self.weight = None
self.bias = None
def forward(self, x: Tensor, channel_axis: int = 1, axes: list[int] | None = None):
"""
Forward method for group norm layer.
Parameters
----------
x : Tensor
The input tensor.
channel_axis : int
Channel axis of the input data.
axes : Optional[List[int]]
Optional list of axes to compute norm over, if not specified,
assumes that the first two axes should be left alone.
Returns
-------
ret : Tensor
The output tensor for the group norm layer.
"""
return op.group_norm(
x, self.num_groups, self.weight, self.bias, self.eps, channel_axis, axes
)
class KVCache(Effect):
"""Managed key-value cache for autoregressive decoding.
``KVCache`` is a TVM-specific ``Effect`` that allocates and maintains a runtime cache
for storing past key/value tensors in transformer models. Unlike regular ``Module``
parameters, effects are registered with the Relax VM and carry mutable state across
calls (append, reset) without being passed as explicit function arguments.
The cache is pre-allocated with shape ``[init_seq_len, *unit_shape]`` and grows via
the ``append`` method at runtime. Use ``init_seq_len`` to control the initial
allocation size.
Parameters
----------
init_seq_len : int
Initial sequence-length capacity of the cache allocation.
unit_shape : Sequence[int]
Shape of a single cache entry excluding the sequence dimension.
For multi-head attention this is typically ``[num_heads, head_dim]``.
dtype : Optional[str]
Data type of the cache tensor. ``None`` uses the default dtype.
"""
init_seq_len: int
unit_shape: list[int]
dtype: str
cache: rx.Var | None
def __init__(
self,
init_seq_len: int,
unit_shape: Sequence[int],
dtype: str | None = None,
):
if dtype is None:
dtype = get_default_dtype()
# Usually the shape is: [init_seq_len, num_heads, head_dim]
# and unit_shape = [num_heads, head_dim]
self.init_seq_len = init_seq_len
self.unit_shape = [int(i) for i in unit_shape]
self.dtype = dtype
def emit_init(self, name_hint: str, bb: rx.BlockBuilder): # pylint: disable=arguments-renamed
"""
Emit the initialization of the KVCache effect.
Parameters
----------
name_hint : str
The name hint of the initialization binding Var.
bb : relax.BlockBuilder
The relax BlockBuilder to emit.
"""
init_shape = rx.ShapeExpr([self.init_seq_len] + self.unit_shape)
return [
bb.emit(
rx.op.call_pure_packed(
"vm.builtin.attention_kv_cache_create",
rx.op.zeros(init_shape, self.dtype),
init_shape,
rx.prim_value(0),
ty_args=rx.ObjectType(),
),
name_hint=name_hint,
)
]
def create(self, name_hint: str) -> list[rx.Var]:
"""
Create the implicit inputs to a relax.Function that represents the KVCache effect.
Parameters
----------
name_hint : str
The name hint of the relax.Var.
Returns
-------
ret : List[relax.Var]
The relax.Var for KVCache.
"""
cache = rx.Var(name_hint, ty=rx.AnyType())
return [cache]
def set_state(self, state_vars: list[rx.Var]) -> None:
(self.cache,) = state_vars
def finalize(self) -> list[rx.Var]:
"""
Finalize the KVCache effect as the implicit return value of a relax.Function.
Returns
-------
ret : List[rx.Var]
The output relax.Var as KVCache.
"""
result = self.cache
self.cache = None
return [result]
def to(self, dtype: str | None = None) -> None:
"""
Convert the KVCache effect to specific dtype.
Parameters
----------
dtype : Optional[str]
The target data type to convert.
"""
if dtype is not None:
self.dtype = dtype
def view(self, seq_len: tirx.Var) -> Tensor:
"""
View the last elements in KVCache.
Parameters
----------
seq_len : tirx.Var
The number of last elements to view.
Returns
-------
ret : Tensor
The last tensor to view.
"""
shape = rx.ShapeExpr([seq_len] + self.unit_shape)
return Tensor(
_expr=rx.BlockBuilder.current().emit(
rx.op.call_pure_packed(
"vm.builtin.attention_kv_cache_view",
self.cache,
shape,
ty_args=rx.TensorType(shape, self.dtype),
)
)
)
def append(self, new_element: Tensor) -> None:
"""
Append a new element in KVCache.
Parameters
----------
new_element : Tensor
The new tensor to append.
"""
if new_element.dtype != self.dtype:
raise TypeError(
f'KVCache has been set to use dtype "{self.dtype}", but got "{new_element.dtype}"'
)
self.cache = rx.BlockBuilder.current().emit(
rx.op.call_inplace_packed(
"vm.builtin.attention_kv_cache_append",
self.cache,
new_element._expr,
inplace_indices=[0],
ty_args=rx.AnyType(),
)
)
class Embedding(Module):
"""A lookup table that retrieves embeddings by index.
Parameters
----------
num : Union[int, str, tirx.Expr]
Size of the embedding dictionary (vocabulary size). Can be symbolic.
dim : Union[int, str, tirx.Expr]
Size of each embedding vector. Can be symbolic.
dtype : Optional[str]
Data type of the embedding weight. ``None`` uses the default dtype.
"""
def __init__(
self,
num: int | str | tirx.Expr,
dim: int | str | tirx.Expr,
dtype: str | None = None,
):
self.num = num
self.dim = dim
self.weight = Parameter((num, dim), dtype=dtype)
def forward(self, x: Tensor):
"""
Forward method for embedding layer.
Parameters
----------
x : Tensor
The input tensor.
Returns
-------
ret : Tensor
The output tensor for the embedding layer.
"""
if x.ndim == 1:
return op.take(self.weight, x, axis=0)
return op.reshape(
op.take(
self.weight,
op.reshape(x, shape=[-1]),
axis=0,
),
shape=[*x.shape, self.weight.shape[1]],
)
class TimestepEmbedding(Module):
"""MLP that projects timestep embeddings, following the HuggingFace diffusers convention.
Consists of two linear layers with an activation in between, and an optional
conditional projection and post-activation.
Parameters
----------
in_channels : int
Dimensionality of the input timestep embedding.
time_embed_dim : int
Dimensionality of the intermediate (hidden) projection.
act_fn : str
Activation function name. Currently only ``"silu"`` is supported.
out_dim : Optional[int]
Dimensionality of the output. If ``None``, defaults to *time_embed_dim*.
post_act_fn : Optional[str]
Optional post-activation applied after the second linear layer.
cond_proj_dim : Optional[int]
If set, adds a linear projection from a conditioning signal of this
dimensionality to *in_channels*, which is added to the input sample.
"""
def __init__(
self,
in_channels: int,
time_embed_dim: int,
act_fn: str = "silu",
out_dim: int | None = None,
post_act_fn: str | None = None,
cond_proj_dim: int | None = None,
):
self.linear_1 = Linear(in_channels, time_embed_dim)
if cond_proj_dim is not None:
self.cond_proj = Linear(cond_proj_dim, in_channels, bias=False)
else:
self.cond_proj = None
assert act_fn == "silu", "Only SiLU activations are supported."
self.act = SiLU()
if out_dim is not None:
time_embed_dim_out = out_dim
else:
time_embed_dim_out = time_embed_dim
self.linear_2 = Linear(time_embed_dim, time_embed_dim_out)
if post_act_fn is None:
self.post_act = None
else:
assert self.post_act == "silu", "Only SiLU post-activation supported."
self.post_act = SiLU()
def forward(self, sample: Tensor, condition: Tensor | None = None):
"""
Forward method for TimestepEmbedding layer.
Parameters
----------
sample : Tensor
The input timestep that should be looked up.
condition : Optional[Tensor]
Optional additional projection matrix.
Returns
-------
ret : Tensor
The resulting embedding lookup for the input sample.
"""
if condition is not None:
sample = sample + self.cond_proj(condition)
sample = self.linear_1(sample)
if self.act is not None:
sample = self.act(sample)
sample = self.linear_2(sample)
if self.post_act is not None:
sample = self.post_act(sample)
return sample
class Timesteps(Module):
"""Sinusoidal positional embedding for diffusion timesteps (HuggingFace convention).
Parameters
----------
num_channels : int
Dimensionality of the embedding (number of sinusoidal channels).
flip_sin_to_cos : bool
If ``True``, swap sin and cos components. Default: ``False``.
downscale_freq_shift : float
Shift applied to the frequency denominator. Default: ``1``.
"""
def __init__(
self, num_channels: int, flip_sin_to_cos: bool = False, downscale_freq_shift: float = 1
):
self.num_channels = num_channels
self.flip_sin_to_cos = flip_sin_to_cos
self.downscale_freq_shift = downscale_freq_shift
def forward(self, x: Tensor):
return op.get_timestep_embedding(
x,
embedding_dim=self.num_channels,
flip_sin_to_cos=self.flip_sin_to_cos,
downscale_freq_shift=self.downscale_freq_shift,
)
class Attention(Module):
"""
A cross attention layer.
Parameters
----------
query_dim : int
The number of channels in the query.
cross_attention_dim : Optional[int]
The number of channels in the encoder_hidden_states.
If not given, defaults to `query_dim`.
heads : int
The number of heads to use for multi-head attention.
dim_head : int
The number of channels in each head.
bias : bool
Set to `True` for the query, key, and value linear layers to contain a bias parameter.
norm_num_groups : Optional[int]
When set, group norm is applied to the input using this number of groups.
out_bias : bool
Set to `True` to apply a bias to the output linear layer.
scale_qk : bool
Whether to apply scaling to query and key tensors.
"""
def __init__(
self,
query_dim: int,
cross_attention_dim: int | None = None,
heads: int = 8,
dim_head: int = 64,
bias: bool = False,
norm_num_groups: int | None = None,
out_bias: bool = True,
scale_qk: bool = True,
):
self.query_dim = query_dim
self.cross_attention_dim = cross_attention_dim if cross_attention_dim else query_dim
self.heads = heads
self.dim_head = dim_head
self.bias = bias
self.norm_num_groups = norm_num_groups
self.out_bias = out_bias
self.scale_qk = scale_qk
self.scale = dim_head**-0.5 if self.scale_qk else 1.0
self.inner_dim = dim_head * heads
self.to_q = Linear(self.query_dim, self.inner_dim, bias=self.bias)
self.to_k = Linear(self.cross_attention_dim, self.inner_dim, bias=self.bias)
self.to_v = Linear(self.cross_attention_dim, self.inner_dim, bias=self.bias)
if self.norm_num_groups is not None:
self.group_norm = GroupNorm(
num_channels=self.query_dim, num_groups=self.norm_num_groups, affine=True
)
else:
self.group_norm = None
self.to_out = ModuleList([Linear(self.inner_dim, self.query_dim, bias=self.out_bias)])
def forward(
self,
hidden_states: Tensor,
encoder_hidden_states: Tensor | None = None,
attention_mask: Tensor | None = None,
**cross_attention_kwargs,
):
"""
Forward method for Attention layer.
Parameters
----------
hidden_states : Tensor
The input sample tensor.
encoder_hidden_states : Optional[Tensor]
Previous hidden step hidden states.
attention_mask : Optional[Tensor]
Mask tensor for attention, currently not supported.
Returns
-------
ret : Tensor
The output tensor for the embedding layer.
"""
# This implementation assumes use of torch 2.0 scaled_dot_product attention.
assert attention_mask is None, "Attention mask not yet supported."
if self.group_norm is not None:
hidden_states = self.group_norm(hidden_states, channel_axis=2, axes=[1])
query = self.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
key = self.to_k(encoder_hidden_states)
value = self.to_v(encoder_hidden_states)
head_dim = int(self.inner_dim // self.heads)
query = op.reshape(query, [0, -1, self.heads, head_dim])
key = op.reshape(key, [0, -1, self.heads, head_dim])
value = op.reshape(value, [0, -1, self.heads, head_dim])
hidden_states = op.scaled_dot_product_attention(query, key, value, is_causal=False)
# Return to proper shape.
hidden_states = op.reshape(hidden_states, (0, -1, self.heads * head_dim))
# Linear projection
hidden_states = self.to_out[0](hidden_states)
return hidden_states