chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
# isort: skip_file
# 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=wildcard-import
"""Neural network operators"""
from .conv1d import *
from .conv2d import *
from .conv3d import *
from .correlation import *
from .deformable_conv2d import *
from .depthwise_conv2d import *
from .elemwise import *
from .dilate import *
from .flatten import *
from .dense import *
from .mapping import *
from .pooling import *
from .softmax import *
from .conv3d_transpose import *
from .conv2d_transpose import *
from .conv1d_transpose import *
from .bnn import *
from .qnn import *
from .upsampling import *
from .instance_norm import instance_norm
from .layer_norm import layer_norm
from .group_norm import group_norm
from .rms_norm import rms_norm
from .local_response_norm import *
from .bitserial_conv2d import *
from .bitserial_dense import *
from .batch_matmul import *
from .batch_norm import *
from .pad import *
from .fifo_buffer import *
from .depth_to_space import *
from .space_to_depth import *
from .space_to_batch_nd import *
from .batch_to_space_nd import *
from .loss import *
from .lstm import *
+152
View File
@@ -0,0 +1,152 @@
# 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.
# ruff: noqa: E731
"""Batch matrix multiplication"""
# pylint: disable=invalid-name
import logging
import tvm
from tvm import te
from ..utils import get_const_tuple
logger = logging.getLogger("topi")
def batch_matmul(
tensor_a,
tensor_b,
oshape=None,
out_dtype=None,
transpose_a=False,
transpose_b=True,
auto_scheduler_rewritten_layout="",
meta_schedule_original_shape=None,
):
"""Compute batch matrix multiplication of `tensor_a` and `tensor_b`.
Both `tensor_a` and `tensor_b` can be transposed. For legacy reason, we use NT format
(transpose_a=False, transpose_b=True) by default.
Parameters
----------
tensor_a : tvm.te.Tensor
3-D with shape [batch, M, K] or [batch, K, M].
tensor_b : tvm.te.Tensor
3-D with shape [batch, K, N] or [batch, N, K].
oshape : List[Optional]
Explicit intended output shape of the computation. Can be useful in cases
with dynamic input shapes.
out_dtype : Optional[str]
Specifies the output data type for mixed precision batch matmul.
transpose_a : Optional[bool] = False
Whether the first tensor is in transposed format.
transpose_b : Optional[bool] = True
Whether the second tensor is in transposed format.
auto_scheduler_rewritten_layout: Optional[str] = ""
The layout after auto-scheduler's layout rewrite pass.
meta_schedule_original_shape: Optional[List[Expr]] = None
The original shape of the tensor
Returns
-------
output : tvm.te.Tensor
3-D with shape [batch, M, N]
"""
assert len(tensor_a.shape) == 3, "tensor_a only support 3-dim"
if transpose_a:
XB, XK, XI = get_const_tuple(tensor_a.shape)
else:
XB, XI, XK = get_const_tuple(tensor_a.shape)
if auto_scheduler_rewritten_layout:
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
if meta_schedule_original_shape:
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
assert len(tensor_b.shape) == 3, "tensor_b only support 3-dim"
if transpose_b:
YB, YJ, YK = get_const_tuple(tensor_b.shape)
else:
YB, YK, YJ = get_const_tuple(tensor_b.shape)
assert XK == YK or isinstance(YK, tvm.tirx.expr.Var), "shapes of x and y are inconsistent"
k = te.reduce_axis((0, XK), name="k")
if oshape is None:
assert XB == YB or XB == 1 or YB == 1, "batch dimension doesn't match"
batch = (
tvm.tirx.expr.Var("batch", "int32")
if isinstance(XB, tvm.tirx.expr.Var) or isinstance(YB, tvm.tirx.expr.Var)
else te.max(XB, YB)
)
oshape = (batch, XI, YJ)
if out_dtype is None:
out_dtype = tensor_a.dtype
if tensor_a.dtype != tensor_b.dtype:
logger.warning(
"tensor_a has different data type with tensor_b: %s, %s",
tensor_a.dtype,
tensor_b.dtype,
)
if (transpose_a, transpose_b) == (True, True):
compute_lambda = lambda b, i, j: te.sum(
tensor_a[b if XB != 1 else 0, k, i].astype(out_dtype)
* tensor_b[b if YB != 1 else 0, j, k].astype(out_dtype),
axis=k,
)
compute_name = "T_batch_matmul_TT"
elif (transpose_a, transpose_b) == (True, False):
compute_lambda = lambda b, i, j: te.sum(
tensor_a[b if XB != 1 else 0, k, i].astype(out_dtype)
* tensor_b[b if YB != 1 else 0, k, j].astype(out_dtype),
axis=k,
)
compute_name = "T_batch_matmul_TN"
elif (transpose_a, transpose_b) == (False, True):
compute_lambda = lambda b, i, j: te.sum(
tensor_a[b if XB != 1 else 0, i, k].astype(out_dtype)
* tensor_b[b if YB != 1 else 0, j, k].astype(out_dtype),
axis=k,
)
compute_name = "T_batch_matmul_NT"
else: # (transpose_a, transpose_b) == (False, False):
compute_lambda = lambda b, i, j: te.sum(
tensor_a[b if XB != 1 else 0, i, k].astype(out_dtype)
* tensor_b[b if YB != 1 else 0, k, j].astype(out_dtype),
axis=k,
)
compute_name = "T_batch_matmul_NN"
output = te.compute(
oshape,
compute_lambda,
name=compute_name,
tag="batch_matmul",
attrs={"layout_free_placeholders": [tensor_b]},
)
if auto_scheduler_rewritten_layout:
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
return output
+146
View File
@@ -0,0 +1,146 @@
# 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.
"""Batch normalization."""
from functools import reduce
from tvm import te, topi
def batch_norm(
data: te.Tensor,
gamma: te.Tensor,
beta: te.Tensor,
moving_mean: te.Tensor,
moving_var: te.Tensor,
axis: int | None = None,
epsilon: float | None = None,
center: bool | None = None,
scale: bool | None = None,
training: bool | None = None,
momentum: float | None = None,
) -> list[te.Tensor]:
"""Batch normalization layer (Ioffe and Szegedy, 2014).
Normalizes the input at each batch, i.e. applies a transformation
that maintains the mean activation close to 0 and the activation
standard deviation close to 1.
Parameters
----------
data : tvm.te.Tensor
Input to be batch-normalized.
gamma : tvm.te.Tensor
Scale factor to be applied to the normalized tensor.
beta : tvm.te.Tensor
Offset to be applied to the normalized tensor.
moving_mean : tvm.te.Tensor
Running mean of input.
moving_var : tvm.te.Tensor
Running variance of input.
axis : int, optional, default=1
Specify along which shape axis the normalization should occur.
epsilon : float, optional, default=1e-5
Small float added to variance to avoid dividing by zero.
center : bool, optional, default=True
If True, add offset of beta to normalized tensor, If False,
beta is ignored.
scale : bool, optional, defualt=True
If True, scale normalized tensor by gamma. If False, gamma
is ignored.
training : bool, optional, defualt=False
Indicating whether it is in training mode. If True, update
moving_mean and moving_var.
momentum : float, optional, default=0.1
The value used for the moving_mean and moving_var update.
Returns
-------
output : list of tvm.te.Tensor
Normalized data with same shape as input
moving_mean : tvm.te.Tensor
Running mean of input.
moving_var : tvm.te.Tensor
Running variance of input.
"""
if axis is None:
axis = 1
if epsilon is None:
epsilon = 1e-5
if center is None:
center = True
if scale is None:
scale = True
if training is None:
training = False
if momentum is None:
momentum = 0.1
shape = [1] * len(data.shape)
shape[axis] = data.shape[axis]
data_mean = None
data_var = None
if training:
reduce_axes = list(range(len(data.shape)))
reduce_axes.remove(axis)
shape_prod = reduce(lambda x, y: x * y, [data.shape[ax] for ax in reduce_axes], 1)
data_mean = topi.sum(data, axis=reduce_axes) / shape_prod
data_mean_rs = topi.reshape(data_mean, shape)
data_var = (
topi.sum((data - data_mean_rs) * (data - data_mean_rs), axis=reduce_axes) / shape_prod
)
data_var_rs = topi.reshape(data_var, shape)
out = (data - data_mean_rs) / topi.math.sqrt(data_var_rs + epsilon)
else:
moving_mean_rs = topi.reshape(moving_mean, shape)
moving_var_rs = topi.reshape(moving_var, shape)
out = (data - moving_mean_rs) / topi.math.sqrt(moving_var_rs + epsilon)
if scale:
out = out * topi.reshape(gamma, shape)
if center:
out = out + topi.reshape(beta, shape)
if training:
assert 0 <= momentum <= 1, "the valid momentum range is [0, 1]."
return [
out,
(1 - momentum) * moving_mean + momentum * data_mean,
(1 - momentum) * moving_var + momentum * data_var,
]
# Moving mean and var aren't updated during test. To avoid
# placeholder reuse, we multiply by 1 and return them.
return [out, moving_mean * 1, moving_var * 1]
+49
View File
@@ -0,0 +1,49 @@
# 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=invalid-name
"""TVM operator batch_to_space_nd compute."""
from . import cpp
def batch_to_space_nd(data, block_shape, crop_begin_list, crop_end_list):
"""Perform space to batch transformation on the data
Parameters
----------
data : tvm.te.Tensor
N-D Tensor with shape [batch, spatial_shape, remaining_shapes],
where spatial_shape has M dimensions.
block_shape : list of ints
list of size [M] where M is number of spatial dims, specifies block
size for each spatial dimension.
crop_begin_list : list of ints
list of shape [M] where M is number of spatial dims, specifies
begin crop size for each spatial dimension.
crop_end_list : list of ints
list of shape [M] where M is number of spatial dims, specifies
end crop size for each spatial dimension.
Returns
-------
output : tvm.te.Tensor
"""
return cpp.nn.batch_to_space_nd(data, block_shape, crop_begin_list, crop_end_list)
+276
View File
@@ -0,0 +1,276 @@
# 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=invalid-name, too-many-locals, too-many-arguments
# pylint: disable=unused-argument, redefined-builtin
"""Bitserial Conv2D operators"""
import tvm
from tvm import te
from ..utils import get_const_tuple
from .bitserial_util import bitpack
from .pad import pad
from .utils import get_pad_tuple
def bitserial_conv2d_nchw(
data,
kernel,
stride,
padding,
activation_bits,
weight_bits,
pack_dtype="uint32",
out_dtype="int16",
unipolar=True,
):
"""Bitserial Conv2D operator.
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, in_channel, in_height, in_width]
kernel : tvm.te.Tensor
4-D with shape [num_filter, in_channel, filter_height, filter_width]
stride : int or a list/tuple of two ints
stride size, or [stride_height, stride_width]
padding : int or a list/tuple of two or four ints
padding size, [pad_height, pad_width], [pad_top, pad_left, pad_down, pad_right]
activation_bits: int
number of bits used for activations/input elements
weight_bits: int
number of bits used for weight elements
out_dtype: str
return type of convolution
pack_dtype: str
bit packing type
unipolar: bool
if binarization style is in unipolar 1/0 format, instead of bipolar -1/+1 format
Returns
-------
output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
assert isinstance(stride, int) or len(stride) == 2
Input_q = bitpack(data, activation_bits, pack_axis=1, bit_axis=2, pack_type=pack_dtype)
if len(kernel.shape) == 4:
Filter_q = bitpack(kernel, weight_bits, pack_axis=1, bit_axis=4, pack_type=pack_dtype)
else:
Filter_q = kernel
batch, in_channel, activation_bits, in_height, in_width = Input_q.shape
num_filter, _, kernel_h, kernel_w, weight_bits = Filter_q.shape
if isinstance(padding, int) or (isinstance(padding, tuple | list) and len(padding) == 2):
TPAD, LPAD, DPAD, RPAD = get_pad_tuple(padding, kernel)
else:
TPAD, LPAD, DPAD, RPAD = padding
pad_before = [0, 0, 0, TPAD, LPAD]
pad_after = [0, 0, 0, DPAD, RPAD]
PadInput_q = pad(Input_q, pad_before, pad_after, name="pad_temp")
# compute the output shape
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
out_channel = num_filter
out_height = (in_height - kernel_h + TPAD + DPAD) // stride_h + 1
out_width = (in_width - kernel_w + LPAD + RPAD) // stride_w + 1
rc = te.reduce_axis((0, in_channel), name="rc")
ry = te.reduce_axis((0, kernel_h), name="ry")
rx = te.reduce_axis((0, kernel_w), name="rx")
b1 = te.reduce_axis((0, activation_bits), name="b1")
b2 = te.reduce_axis((0, weight_bits), name="b2")
if unipolar:
def _conv(nn, ff, yy, xx):
b1b2 = (b1 + b2).astype(out_dtype)
return te.sum(
(
(
tvm.tirx.popcount(
PadInput_q[nn, rc, b1, yy * stride_h + ry, xx * stride_w + rx]
& Filter_q[ff, rc, ry, rx, b2]
)
- tvm.tirx.popcount(
PadInput_q[nn, rc, b1, yy * stride_h + ry, xx * stride_w + rx]
& ~Filter_q[ff, rc, ry, rx, b2]
)
)
<< (b1b2)
).astype(out_dtype),
axis=[rc, ry, rx, b2, b1],
).astype(out_dtype)
else:
def _conv(nn, ff, yy, xx):
b1b2 = (b1 + b2).astype(out_dtype)
return te.sum(
(
tvm.tirx.popcount(
PadInput_q[nn, rc, b1, yy * stride_h + ry, xx * stride_w + rx]
& Filter_q[ff, rc, ry, rx, b2]
)
<< (b1b2)
).astype(out_dtype),
axis=[rc, ry, rx, b2, b1],
).astype(out_dtype)
return te.compute(
(batch, out_channel, out_height, out_width),
_conv,
name="Conv2dOutput",
tag="bitserial_conv2d_nchw",
)
def bitserial_conv2d_nhwc(
data,
kernel,
stride,
padding,
activation_bits,
weight_bits,
pack_dtype="uint32",
out_dtype="int16",
unipolar=True,
):
"""Bitserial Conv2D operator.
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, in_height, in_width, in_channel]
kernel : tvm.te.Tensor
4-D with shape [filter_height, filter_width, in_channel, num_filter]
stride : int or a list/tuple of two ints
stride size, or [stride_height, stride_width]
padding : int or a list/tuple of two or four ints
padding size, [pad_height, pad_width], [pad_top, pad_left, pad_down, pad_right]
activation_bits: int
number of bits used for activations/input elements
weight_bits: int
number of bits used for weight elements
out_dtype: str
return type of convolution
pack_dtype: str
bit packing type
unipolar: bool
if binarization style is in unipolar 1/0 format, instead of bipolar -1/+1 format
Returns
-------
output : tvm.te.Tensor
4-D with shape [batch, out_height, out_width, out_channel]
"""
assert isinstance(stride, int) or len(stride) == 2
Input_q = bitpack(data, activation_bits, pack_axis=3, bit_axis=4, pack_type=pack_dtype)
if len(kernel.shape) == 4:
Filter_q = bitpack(kernel, weight_bits, pack_axis=2, bit_axis=4, pack_type=pack_dtype)
kernel_h, kernel_w, _, num_filter, _ = get_const_tuple(Filter_q.shape)
else:
Filter_q = kernel
kernel_h, kernel_w, _, _, num_filter = get_const_tuple(Filter_q.shape)
batch, in_height, in_width, in_channel_q, _ = get_const_tuple(Input_q.shape)
if isinstance(padding, int) or (isinstance(padding, tuple | list) and len(padding) == 2):
TPAD, LPAD, DPAD, RPAD = get_pad_tuple(padding, kernel)
else:
TPAD, LPAD, DPAD, RPAD = padding
pad_before = [0, TPAD, LPAD, 0, 0]
pad_after = [0, DPAD, RPAD, 0, 0]
# compute the output shape
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
out_channel = num_filter
out_height = (in_height - kernel_h + TPAD + DPAD) // stride_h + 1
out_width = (in_width - kernel_w + LPAD + RPAD) // stride_w + 1
PadInput_q = pad(Input_q, pad_before, pad_after, name="PaddedInput")
rc = te.reduce_axis((0, in_channel_q), name="rc")
ry = te.reduce_axis((0, kernel_h), name="ry")
rx = te.reduce_axis((0, kernel_w), name="rx")
b1 = te.reduce_axis((0, activation_bits), name="b1")
b2 = te.reduce_axis((0, weight_bits), name="b2")
if unipolar:
def _conv(nn, yy, xx, ff):
b1b2 = (b1 + b2).astype(out_dtype)
return te.sum(
(
(
tvm.tirx.popcount(
PadInput_q[nn, yy * stride_h + ry, xx * stride_w + rx, rc, b1]
& Filter_q[ry, rx, rc, ff, b2]
)
- tvm.tirx.popcount(
PadInput_q[nn, yy * stride_h + ry, xx * stride_w + rx, rc, b1]
& ~Filter_q[ry, rx, rc, ff, b2]
)
)
<< b1b2
).astype(out_dtype),
axis=[rc, ry, rx, b2, b1],
)
else:
def _conv(nn, yy, xx, ff):
b1b2 = (b1 + b2).astype(out_dtype)
return te.sum(
(
tvm.tirx.popcount(
PadInput_q[nn, yy * stride_h + ry, xx * stride_w + rx, rc, b1]
& Filter_q[ry, rx, rc, ff, b2]
)
<< b1b2
).astype(out_dtype),
axis=[rc, ry, rx, b2, b1],
)
conv = te.compute(
(batch, out_height, out_width, out_channel),
_conv,
name="Conv2dOutput",
tag="bitserial_conv2d_nhwc",
)
return conv
+82
View File
@@ -0,0 +1,82 @@
# 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=invalid-name, too-many-locals, too-many-arguments
"""Bitserial Dense operator."""
import tvm
from tvm import te
from tvm.topi.utils import get_const_tuple
from .bitserial_util import bitpack
def bitserial_dense(
data, weight, data_bits, weight_bits, pack_dtype="uint32", out_dtype="int16", unipolar=True
):
"""The default implementation of bitserial dense in topi.
Parameters
----------
data : tvm.te.Tensor
2-D with shape [batch, in_dim]
weight : tvm.te.Tensor
2-D with shape [out_dim, in_dim] or
3-D with shape [out_dim, weight_bits, in_dim]
Returns
-------
output : tvm.te.Tensor
2-D with shape [batch, out_dim]
"""
data_packed = bitpack(data, data_bits, pack_axis=1, bit_axis=1, pack_type=pack_dtype)
if len(weight.shape) == 2:
weight_packed = bitpack(weight, weight_bits, pack_axis=1, bit_axis=1, pack_type=pack_dtype)
else:
weight_packed = weight
Y, DB, K = get_const_tuple(data_packed.shape)
X, WB, _ = get_const_tuple(weight_packed.shape)
oshape = (Y, X)
k = te.reduce_axis((0, K), name="k")
db = te.reduce_axis((0, DB), name="db")
wb = te.reduce_axis((0, WB), name="wb")
matmul_unipolar = te.compute(
oshape,
lambda i, j: te.sum(
(
tvm.tirx.popcount(weight_packed[j, wb, k] & data_packed[i, db, k])
- tvm.tirx.popcount(~weight_packed[j, wb, k] & data_packed[i, db, k])
).astype(out_dtype)
<< (db + wb).astype(out_dtype),
axis=[wb, db, k],
),
tag="bitserial_dense_unipolar",
)
matmul = te.compute(
oshape,
lambda i, j: te.sum(
tvm.tirx.popcount(weight_packed[j, wb, k] & data_packed[i, db, k]).astype(out_dtype)
<< (db + wb).astype(out_dtype),
axis=[wb, db, k],
),
tag="bitserial_dense",
)
if unipolar:
return matmul_unipolar
return matmul
+110
View File
@@ -0,0 +1,110 @@
# 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=invalid-name, too-many-locals, too-many-arguments
"""Utility functions for bitserial operators"""
import numpy as np
import tvm
from tvm import te
from tvm.topi.transform import concatenate
from ..utils import get_const_int
def bitpack(data, bits, pack_axis, bit_axis, pack_type, name="QuantizeInput"):
"""Packs data into format necessary for bitserial computation
Parameters
----------
data : tvm.te.Tensor
The input tvm tensor
bits : int
Number of bits to use for packing
pack_axis : int
index of the axis to pack in data
bit_axis : int
index of axis to place bit axis in resulting packed data
pack_type : str
Data type for packing, must be one of: ['uint8', 'uint16', 'uint32', 'uint64']
name : Optional[str] = "QuantizeInput"
Name for the operation
"""
ishape = data.shape
n = len(ishape)
if pack_type == "uint8":
data_width = 8
elif pack_type == "uint16":
data_width = 16
elif pack_type == "uint32":
data_width = 32
elif pack_type == "uint64":
data_width = 64
# Data must be in multiples of the data_width
assert get_const_int(ishape[pack_axis]) % data_width == 0, "Not a multiple of word size"
shape_vec = list(ishape)
shape_vec[pack_axis] = shape_vec[pack_axis] // data_width
shape_vec.insert(bit_axis, 1)
bitserial_oshape = tuple(shape_vec)
masks = np.array([0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80])
# pack axis shifts if bit axis comes before
if bit_axis <= pack_axis:
pack_axis += 1
def _bitpack(*indices):
packed_data = [tvm.tirx.const(0, pack_type)] * bits
for k in range(data_width):
# Translate indices for packed data back to original
idx = [0] * n
j = 0
for i in range(n + 1):
if i == bit_axis:
continue
if i == pack_axis:
idx[j] = indices[i] * data_width + k
else:
idx[j] = indices[i]
j += 1
element = data(*idx)
for b in range(bits):
extracted_bit = ((element & tvm.tirx.const(masks[b], "int32")) >> b).astype(
pack_type
)
packed_data[b] = packed_data[b] | extracted_bit
if k < data_width - 1:
packed_data[b] = packed_data[b] << 1
if k == data_width - 1:
return tuple(packed_data)
return tuple(packed_data)
output_tuple = te.compute(bitserial_oshape, _bitpack, name=name, tag="bitpack")
if bits > 1:
return concatenate(output_tuple, axis=bit_axis)
return output_tuple
def binary_op_multiplier(pack_dtype):
""" "Returns number of bits packed into
pack_dtype: string
pack type for the operator (must be a uint)"""
return int(pack_dtype[4:])
+99
View File
@@ -0,0 +1,99 @@
# 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.
"""Binary Neural Network (BNN) Operators"""
import tvm
from tvm import te
from .. import tag
from ..utils import get_const_int, simplify
def binarize_pack(data, axis=None, name="PackedInput"):
"""Binarization and bit-packing along a certain axis.
Parameters
----------
data : tvm.te.Tensor
n-D input, can be any layout.
axis : None or int
The axis along which to do binarization and bit-packing,
default is the last axis.
name : str, optional
The name prefix operators generate.
Returns
-------
output : tvm.te.Tensor
n-D, the same layout as input, dtype is uint32.
"""
ishape = data.shape
if axis is None:
axis = len(ishape) - 1
assert get_const_int(ishape[axis]) % 32 == 0
n = len(ishape)
oshape = tuple(simplify(ishape[i] // 32) if i == axis else ishape[i] for i in range(n))
def _binarize_pack(*indices):
start_idx = [indices[i] * 32 if i == axis else indices[i] for i in range(n)]
packed = tvm.tirx.const(0, "uint32")
for j in range(32):
idx = [start_idx[i] + j if i == axis else start_idx[i] for i in range(n)]
sign = (data(*idx) >= 0).astype("uint32")
packed = packed | sign
if j == 31:
return packed
packed = packed << 1
raise RuntimeError("not resach")
return te.compute(oshape, _binarize_pack, name=name, tag="binarize_pack")
def binary_dense(data, weight):
"""Binary matrix multiplication using xor and bit-count.
Parameters
----------
data : tvm.te.Tensor
2-D with shape [batch, in_dim], dtype is uint32.
weight : tvm.te.Tensor
2-D with shape [out_dim, in_dim], dtype is uint32.
Returns
-------
output : tvm.te.Tensor
2-D with shape [batch, out_dim], dtype is float32.
"""
assert data.dtype == "uint32" and weight.dtype == "uint32", (
"dtype of data and weight should be uint32"
)
assert len(data.shape) == 2 and len(weight.shape) == 2, "only support 2-dim binary dense"
batch, in_dim = data.shape
out_dim, _ = weight.shape
k = te.reduce_axis((0, in_dim), name="k")
matmul = te.compute(
(batch, out_dim),
lambda i, j: te.sum(tvm.tirx.popcount(data[i, k] ^ weight[j, k]), axis=k),
tag="binary_dense",
)
return te.compute(
(batch, out_dim), lambda i, j: 32 * in_dim - 2.0 * matmul(i, j), tag=tag.ELEMWISE
)
+141
View File
@@ -0,0 +1,141 @@
# 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=invalid-name, unused-variable, unused-argument
"""1D convolution operators."""
from .conv2d import conv
def conv1d(
data,
kernel,
strides=1,
padding="VALID",
dilation=1,
groups=1,
data_layout="NCW",
kernel_layout="",
out_dtype=None,
):
"""1D convolution forward operator.
Parameters
----------
data : tvm.te.Tensor
3-D input shape [batch, in_channel, in_width] for data_layout == 'NCW'
and [batch, in_width, in_channel] for data_layout == 'NWC'
kernel : tvm.te.Tensor
3-D kernel with shape [num_filter, in_channel, filter_size] for kernel_layout == 'OIW'
and [filter_size, in_channel, num_filter] for kernel_layout == 'WIO'
strides : int or tuple
The spatial stride along width
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation : int or tuple
Dilation rate if convolution should be dilated.
data_layout : str
How input data is laid out, must be one of ['NCW', 'NWC']
kernel_layout: Optiona[str]
The layout of the kernel. If unspecified, use default layout. "OIW" if data_layout == "NCW",
"WIO" if data_layout == "NWC".
out_dtype : str
The output data type. If None then output is same type as input.
"""
return conv(
data, kernel, strides, padding, dilation, groups, data_layout, kernel_layout, out_dtype
)
def conv1d_nwc(data, kernel, strides=1, padding="VALID", dilation=1, out_dtype=None):
"""1D convolution in NWC layout. See :py:func:`conv` for details on parameters"""
return conv(data, kernel, strides, padding, dilation, 1, "NWC", "WIO", out_dtype=out_dtype)
def conv1d_ncw(data, kernel, strides=1, padding="VALID", dilation=1, out_dtype=None):
"""1D convolution in NCW layout. See :py:func:`conv` for details on parameters"""
return conv(data, kernel, strides, padding, dilation, 1, "NCW", "OIW", out_dtype=out_dtype)
def group_conv1d_nwc(
data, kernel, strides=1, padding="VALID", dilation=1, groups=1, out_dtype=None
):
"""1D convolution forward operator for NWC layout.
Parameters
----------
data : tvm.te.Tensor
3-D with shape [batch, in_width, in_channel]
kernel : tvm.te.Tensor
3-D with shape [filter_size, in_channel, num_filter]
strides : int or tuple
The spatial stride along width
padding : int, tuple, or str
Padding size can be an integer for equal padding,
a tuple of (left, right) or a string in ['VALID', 'SAME'].
dilation : int or tuple
Dilation rate if convolution should be dilated.
groups : int
Number of groups
out_dtype : str
The output data type. If None then output is same type as input.
"""
return conv(data, kernel, strides, padding, dilation, groups, "NWC", "WIO", out_dtype=out_dtype)
def group_conv1d_ncw(
data, kernel, strides=1, padding="VALID", dilation=1, groups=1, out_dtype=None
):
"""1D convolution forward operator for NCW layout.
Parameters
----------
data : tvm.te.Tensor
3-D with shape [batch, in_channel, in_width]
kernel : tvm.te.Tensor
3-D with shape [num_filter, in_channel, filter_size]
strides : int or tuple
The spatial stride along width
padding : int, tuple, or str
Padding size can be an integer for equal padding,
a tuple of (left, right) or a string in ['VALID', 'SAME'].
dilation : int or tuple
Dilation rate if convolution should be dilated.
groups : int
Number of groups
out_dtype : str
The output data type. If None then output is same type as input.
"""
return conv(data, kernel, strides, padding, dilation, groups, "NCW", "OIW", out_dtype=out_dtype)
+217
View File
@@ -0,0 +1,217 @@
# 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=invalid-name, unused-variable, unused-argument
"""Transposed 1D convolution operators (sometimes called Deconvolution)."""
from tvm import te
from ..utils import simplify
from .dilate import dilate
from .pad import pad
from .utils import get_pad_tuple1d
def _conv1d_transpose_ncw_preprocess(data, kernel, stride, padding, out_dtype, output_padding):
"""Preprocess data and kernel to make the compute pattern
of conv1d_transpose the same as conv1d.
Parameters
----------
data : tvm.te.Tensor
3-D with shape [batch, in_channel, in_width]
kernel : tvm.te.Tensor
3-D with shape [in_channel, num_filter, filter_width]
stride : ints
The spatial stride along width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : ints
Used to recover the actual output shape in case there are more
than one possible shape. Must be smaller than stride.
Returns
-------
data_pad : tvm.te.Tensor
Padded input data. 3-D with shape [batch, in_channel, in_width]
kernel: tvm.te.Tensor
Transformed kernel. 3-D with shape [num_filter, in_channel, filter_width]
"""
# some pre-processing and prelimnary checks
if out_dtype is None:
out_dtype = data.dtype
# dilate and pad
if isinstance(stride, tuple | list):
stride = stride[0]
if isinstance(output_padding, tuple | list):
output_padding = output_padding[0]
_, channels_in, _ = data.shape
_, channels_out, kernel_width = kernel.shape
assert output_padding < stride
channels_out = simplify(channels_out)
data_dilate = dilate(data, [1, 1, stride], name="data_dilate")
pad_left, pad_right = get_pad_tuple1d(padding, (kernel_width,))
pad_left = kernel_width - 1 - pad_left
pad_right = kernel_width - 1 - pad_right + output_padding
data_pad = pad(data_dilate, [0, 0, pad_left], [0, 0, pad_right], name="data_pad")
# transform kernel layout from IOW to OIW, and rotate kernel by 180 degrees
kernel = te.compute(
(channels_out, channels_in, kernel_width),
lambda o, i, w: kernel[i][o][kernel_width - 1 - w],
name="kernel",
)
return data_pad, kernel
def conv1d_transpose_ncw(data, kernel, stride, padding, out_dtype, output_padding):
"""Transposed 1D convolution ncw forward operator.
Parameters
----------
data : tvm.te.Tensor
3-D with shape [batch, in_channel, in_width]
kernel : tvm.te.Tensor
3-D with shape [in_channel, num_filter, filter_width]
stride : ints
The spatial stride along width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : ints
Used to recover the actual output shape in case there are more
than one possible shape. Must be smaller than stride.
Returns
-------
output : tvm.te.Tensor
3-D with shape [batch, out_channel, out_width]
"""
batch, channels_in, _ = data.shape
_, channels_out, kernel_width = kernel.shape
data_pad, transformed_kernel = _conv1d_transpose_ncw_preprocess(
data, kernel, stride, padding, out_dtype, output_padding
)
# convolution
_, _, data_width = data_pad.shape
out_w = simplify(data_width - kernel_width + 1)
dc = te.reduce_axis((0, channels_in), name="dc")
dw = te.reduce_axis((0, kernel_width), name="dw")
output = te.compute(
(batch, channels_out, out_w),
lambda b, c, w: te.sum(
data_pad[b, dc, w + dw].astype(out_dtype)
* transformed_kernel[c, dc, dw].astype(out_dtype),
axis=[dc, dw],
),
tag="conv1d_transpose_ncw",
)
return output
def group_conv1d_transpose_ncw(data, kernel, stride, padding, out_dtype, output_padding, groups):
"""Transposed 1D group convolution ncw forward operator.
Parameters
----------
data : tvm.te.Tensor
3-D with shape [batch, in_channel, in_width]
kernel : tvm.te.Tensor
3-D with shape [in_channel, num_filter, filter_width]
stride : ints
The spatial stride along width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : ints
Used to recover the actual output shape in case there are more
than one possible shape. Must be smaller than stride.
groups : int
number of groups
Returns
-------
output : tvm.te.Tensor
3-D with shape [batch, out_channel, out_width]
"""
if groups == 1:
return conv1d_transpose_ncw(data, kernel, stride, padding, out_dtype, output_padding)
_, in_channels, _ = data.shape
assert in_channels % groups == 0, (
f"input channels {in_channels} must divide group size {groups}"
)
data_pad, transformed_kernel = _conv1d_transpose_ncw_preprocess(
data, kernel, stride, padding, out_dtype, output_padding
)
batch, in_channels, in_w = data_pad.shape
out_c, _, filter_w = transformed_kernel.shape
# convolution stage
out_channels = simplify(out_c * groups)
out_w = simplify(in_w - filter_w + 1)
dc = te.reduce_axis((0, in_channels // groups), name="dc")
dw = te.reduce_axis((0, filter_w), name="dw")
# data: batch, in_channels, out_w
# weight: out_channels // G, in_channels, out_w
return te.compute(
(batch, out_channels, out_w),
lambda b, c, w: te.sum(
data_pad[
b, c // (out_channels // groups) * (in_channels // groups) + dc, w + dw
].astype(out_dtype)
* transformed_kernel[
c % (out_channels // groups),
c // (out_channels // groups) * (in_channels // groups) + dc,
dw,
].astype(out_dtype),
axis=[dc, dw],
),
tag="group_conv1d_transpose_ncw",
)
File diff suppressed because it is too large Load Diff
+246
View File
@@ -0,0 +1,246 @@
# 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=invalid-name, unused-variable, unused-argument
# ruff: noqa: F821
"""Transposed 2D convolution operators (sometimes called Deconvolution)."""
import collections
from tvm import te
from ..utils import simplify
from .dilate import dilate
from .pad import pad
from .utils import get_pad_tuple
def _ntuple(n):
def parse(x):
if isinstance(x, collections.abc.Iterable):
assert len(x) == n, f"Input can only have {n} elements, but got {len(x)} instead: {x}."
return x
return tuple(repeat(x, n))
return parse
_single = _ntuple(1)
_pair = _ntuple(2)
_triple = _ntuple(3)
_quadruple = _ntuple(4)
def conv2d_transpose_nchw(Input, Filter, strides, padding, out_dtype, output_padding):
"""Transposed 2D convolution nchw forward operator.
Parameters
----------
Input : tvm.te.Tensor
4-D with shape [batch, in_channel, in_height, in_width]
Filter : tvm.te.Tensor
4-D with shape [in_channel, num_filter, filter_height, filter_width]
strides : tuple of two ints
The spatial stride along height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : tuple of ints
Used to get the right output shape for gradients
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
return declaration_conv2d_transpose_impl(
Input, Filter, strides, padding, out_dtype, output_padding=output_padding
)
def conv2d_transpose_nchw_preprocess(data, kernel, strides, padding, out_dtype, output_padding):
"""Preprocess data and kernel to make the compute pattern
of conv2d_transpose the same as conv2d"""
batch, in_c, in_h, in_w = data.shape
_, out_c, filter_h, filter_w = kernel.shape
stride_h, stride_w = strides
opad_h, opad_w = output_padding
assert opad_h < stride_h and opad_w < stride_w
# dilate data
data_dilate = dilate(data, [1, 1, stride_h, stride_w], name="data_dilate")
# pad data
fpad_top, fpad_left, fpad_bottom, fpad_right = get_pad_tuple(padding, (filter_h, filter_w))
bpad_top = filter_h - 1 - fpad_top
bpad_bottom = filter_h - 1 - fpad_bottom + opad_h
bpad_left = filter_w - 1 - fpad_left
bpad_right = filter_w - 1 - fpad_right + opad_w
data_pad = pad(
data_dilate, [0, 0, bpad_top, bpad_left], [0, 0, bpad_bottom, bpad_right], name="data_pad"
)
# transform kernel layout from IOHW to OIHW, and rotate kernel by 180 degrees
kernel_transform = te.compute(
(out_c, in_c, filter_h, filter_w),
lambda o, i, h, w: kernel[i][o][filter_h - 1 - h][filter_w - 1 - w],
name="kernel_transform",
)
return data_pad, kernel_transform
def declaration_conv2d_transpose_impl(data, kernel, strides, padding, out_dtype, output_padding):
"""Implementation of conv2d transpose"""
data_pad, kernel_transform = conv2d_transpose_nchw_preprocess(
data, kernel, strides, padding, out_dtype, output_padding
)
batch, in_c, in_h, in_w = data_pad.shape
out_c, _, filter_h, filter_w = kernel_transform.shape
# convolution stage
out_c = simplify(out_c)
out_h = simplify(in_h - filter_h + 1)
out_w = simplify(in_w - filter_w + 1)
dc = te.reduce_axis((0, in_c), name="dc")
dh = te.reduce_axis((0, filter_h), name="dh")
dw = te.reduce_axis((0, filter_w), name="dw")
Output = te.compute(
(batch, out_c, out_h, out_w),
lambda b, c, h, w: te.sum(
data_pad[b, dc, h + dh, w + dw].astype(out_dtype)
* kernel_transform[c, dc, dh, dw].astype(out_dtype),
axis=[dc, dh, dw],
),
tag="conv2d_transpose_nchw",
)
return Output
def group_conv2d_transpose_nchw(data, kernel, stride, padding, out_dtype, output_padding, groups):
"""Group convolution operator in NCHW layout.
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, in_channel, in_height, in_width]
kernel : tvm.te.Tensor
4-D with shape [in_channel, out_channel // groups, filter_height, filter_width]
stride : int or a list/tuple of two ints
Stride size, or [stride_height, stride_width]
padding : int or a list/tuple of 2 or 4 ints
padding size, or
[pad_height, pad_width] for 2 ints, or
[pad_top, pad_left, pad_bottom, pad_right] for 4 ints
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : tuple of ints
Used to get the right output shape for gradients
groups : int
number of groups
out_dtype : str
The output type. This is used for mixed precision.
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
if groups == 1:
return conv2d_transpose_nchw(data, kernel, stride, padding, out_dtype, output_padding)
# some pre-processing and prelimnary checks
if out_dtype is None:
out_dtype = data.dtype
batch, in_channels, in_h, in_w = data.shape
_, out_c, filter_h, filter_w = kernel.shape
assert in_channels % groups == 0, (
f"input channels {in_channels} must divide group size {groups}"
)
# assert out_c % groups == 0, f"output channels {in_c} must divide group size {groups}"
strides = _pair(stride)
# padding = _pair(padding)
# output_padding = _pair(output_padding)
# dilation = _pair(dilation)
stride_h, stride_w = strides
opad_h, opad_w = output_padding
assert opad_h < stride_h and opad_w < stride_w, (
f"[{output_padding}] opad_h:{opad_h} < stride_h:{stride_h} \
and opad_w:{opad_w} < stride_w:{stride_w} does not satisfy."
)
# dilate data
data_dilate = dilate(data, [1, 1, stride_h, stride_w], name="data_dilate")
# pad data
fpad_top, fpad_left, fpad_bottom, fpad_right = get_pad_tuple(padding, (filter_h, filter_w))
bpad_top = filter_h - 1 - fpad_top
bpad_bottom = filter_h - 1 - fpad_bottom + opad_h
bpad_left = filter_w - 1 - fpad_left
bpad_right = filter_w - 1 - fpad_right + opad_w
data_pad = pad(
data_dilate, [0, 0, bpad_top, bpad_left], [0, 0, bpad_bottom, bpad_right], name="data_pad"
)
# transform kernel layout from IOHW to OIHW, and rotate kernel by 180 degrees
kernel_transform = te.compute(
(out_c, in_channels, filter_h, filter_w),
lambda i, o, h, w: kernel[o][i][filter_h - 1 - h][filter_w - 1 - w],
name="kernel_transform",
)
batch, in_channels, in_h, in_w = data_pad.shape
out_c, _, filter_h, filter_w = kernel_transform.shape
# convolution stage
out_channels = simplify(out_c * groups)
out_h = simplify(in_h - filter_h + 1)
out_w = simplify(in_w - filter_w + 1)
dc = te.reduce_axis((0, in_channels // groups), name="dc")
dh = te.reduce_axis((0, filter_h), name="dh")
dw = te.reduce_axis((0, filter_w), name="dw")
# data: batch, in_channels, out_h, out_w
# weight: out_channels // G, in_channels, out_h, out_w
return te.compute(
(batch, out_channels, out_h, out_w),
lambda b, c, h, w: te.sum(
data_pad[
b, c // (out_channels // groups) * (in_channels // groups) + dc, h + dh, w + dw
].astype(out_dtype)
* kernel_transform[
c % (out_channels // groups),
c // (out_channels // groups) * (in_channels // groups) + dc,
dh,
dw,
].astype(out_dtype),
axis=[dc, dh, dw],
),
tag="group_conv2d_transpose_nchw",
)
+169
View File
@@ -0,0 +1,169 @@
# 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=invalid-name, unused-variable, too-many-locals
# pylint: disable=unused-argument, redefined-builtin, no-else-return
"""Conv3D operators"""
from tvm import te
from ..utils import get_const_tuple
from .conv2d import conv
from .winograd_util import winograd_transform_matrices
def conv3d_ncdhw(Input, Filter, stride, padding, dilation, groups, out_dtype=None):
"""Conv3D operator in NCDHW layout.
Parameters
----------
Input : tvm.te.Tensor
5-D with shape [batch, in_channel, in_depth, in_height, in_width]
Filter : tvm.te.Tensor
5-D with shape [num_filter, in_channel, filter_depth, filter_height, filter_width]
stride : int or a list/tuple of three ints
Stride size, or [strid_depth, stride_height, stride_width]
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation: int or a list/tuple of three ints
dilation size, or [dilation_depth, dilation_height, dilation_width]
groups: int
Number of groups.
Returns
-------
Output : tvm.te.Tensor
5-D with shape [batch, out_channel, out_depth, out_height, out_width]
"""
return conv(Input, Filter, stride, padding, dilation, groups, "NCDHW", "OIDHW", out_dtype)
def conv3d_ndhwc(
Input,
Filter,
stride,
padding,
dilation,
groups,
out_dtype="float32",
auto_scheduler_rewritten_layout="",
meta_schedule_origin_shape=None,
):
"""Convolution operator in NDHWC layout.
Parameters
----------
Input : tvm.te.Tensor
5-D with shape [batch, in_depth, in_height, in_width, in_channel]
Filter : tvm.te.Tensor
5-D with shape [filter_depth, filter_height, filter_width, in_channel, num_filter]
stride : int or a list/tuple of three ints
Stride size, or [stride_depth, stride_height, stride_width]
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation: int or a list/tuple of three ints
dilation size, or [dilation_depth, dilation_height, dilation_width]
groups: int
Number of groups.
out_dtype: str = "float32",
The type of output tensor
auto_scheduler_rewritten_layout: str = ""
The layout after auto-scheduler's layout rewrite pass.
meta_schedule_origin_shape: Optional[List[Expr]] = None
The original shape of the input tensor.
Returns
-------
Output : tvm.te.Tensor
5-D with shape [batch, out_depth, out_height, out_width, out_channel]
"""
return conv(
Input,
Filter,
stride,
padding,
dilation,
groups,
"NDHWC",
"DHWIO",
out_dtype,
auto_scheduler_rewritten_layout,
meta_schedule_origin_shape,
)
def conv3d_winograd_weight_transform(kernel, tile_size):
"""Weight transformation for 3D winograd
Parameters
----------
kernel: Tensor
The raw kernel tensor with layout "NCDHW".
tile_size: int
Tile size of winograd transform. e.g. 2 for F(2x2, 3x3) and 4 for F(4x4, 3x3)
Returns
-------
output : tvm.te.Tensor
5-D with shape [alpha, alpha, alpha, CO, CI]
"""
CO, CI, KD, KH, KW = get_const_tuple(kernel.shape)
depth_transform = 2 < KD < 8 and KD == KH
if depth_transform:
assert KD == KH == KW, "Only support NxNxN kernel"
else:
assert KH == KW, "Only supports DxNxN kernel"
r = tile_size + KH - 1
r_kh = te.reduce_axis((0, KH), name="r_kh")
r_kw = te.reduce_axis((0, KW), name="r_kw")
_, _, G = winograd_transform_matrices(tile_size, KH, kernel.dtype)
if depth_transform:
shape = (r, r, r, CO, CI)
r_kd = te.reduce_axis((0, KD), name="r_kd")
return te.compute(
shape,
lambda omg, eps, nu, co, ci: te.sum(
kernel[co][ci][r_kd][r_kh][r_kw] * G[omg][r_kd] * G[eps][r_kh] * G[nu][r_kw],
axis=[r_kd, r_kh, r_kw],
),
name="transform_weight",
)
else:
shape = (r, r, KD, CO, CI)
return te.compute(
shape,
lambda eps, nu, d, co, ci: te.sum(
kernel[co][ci][d][r_kh][r_kw] * G[eps][r_kh] * G[nu][r_kw], axis=[r_kh, r_kw]
),
name="transform_weight",
)
+200
View File
@@ -0,0 +1,200 @@
# 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=invalid-name, unused-variable, unused-argument
"""Transposed 3D convolution operators (sometimes called Deconvolution)."""
from tvm import te
from ..utils import simplify
from .dilate import dilate
from .pad import pad
from .utils import get_pad_tuple3d
def conv3d_transpose_ncdhw(Input, Filter, strides, padding, out_dtype, output_padding):
"""Transposed 3D convolution ncdhw forward operator.
Parameters
----------
Input : tvm.te.Tensor
5-D with shape [batch, in_channel, in_depth, in_height, in_width]
Filter : tvm.te.Tensor
5-D with shape [in_channel, num_filter, filter_depth, filter_height, filter_width]
strides : int or a list/tuple of three ints
The spatial stride along depth,height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : tuple of ints
Used to get the right output shape for gradients
Returns
-------
Output : tvm.te.Tensor
5-D with shape [batch, out_channel, out_depth, out_height, out_width]
"""
return declaration_conv3d_transpose_impl(
Input, Filter, strides, padding, out_dtype, output_padding
)
def conv3d_transpose_ncdhw_preprocess(data, kernel, strides, padding, out_dtype, output_padding):
"""Preprocess data and kernel to make the compute pattern
of conv3d_transpose the same as conv3d"""
batch, in_c, in_d, in_h, in_w = data.shape
_, out_c, filter_d, filter_h, filter_w = kernel.shape
stride_d, stride_h, stride_w = strides
opad_d, opad_h, opad_w = output_padding
assert opad_d < stride_d and opad_h < stride_h and opad_w < stride_w
# dilate data
data_dilate = dilate(data, [1, 1, stride_d, stride_h, stride_w], name="data_dilate")
# pad data
fpad_front, fpad_top, fpad_left, fpad_back, fpad_bottom, fpad_right = get_pad_tuple3d(
padding, (filter_d, filter_h, filter_w)
)
bpad_front = filter_d - 1 - fpad_front
bpad_back = filter_d - 1 - fpad_back + opad_d
bpad_top = filter_h - 1 - fpad_top
bpad_bottom = filter_h - 1 - fpad_bottom + opad_h
bpad_left = filter_w - 1 - fpad_left
bpad_right = filter_w - 1 - fpad_right + opad_w
data_pad = pad(
data_dilate,
[0, 0, bpad_front, bpad_top, bpad_left],
[0, 0, bpad_back, bpad_bottom, bpad_right],
name="data_pad",
)
# transform kernel layout from IODHW to OIDHW, and rotate kernel by 180 degrees
kernel_transform = te.compute(
(out_c, in_c, filter_d, filter_h, filter_w),
lambda o, i, d, h, w: kernel[i][o][filter_d - 1 - d][filter_h - 1 - h][filter_w - 1 - w],
name="kernel_transform",
)
return data_pad, kernel_transform
def declaration_conv3d_transpose_impl(data, kernel, strides, padding, out_dtype, output_padding):
"""Implementation of conv3d transpose"""
data_pad, kernel_transform = conv3d_transpose_ncdhw_preprocess(
data, kernel, strides, padding, out_dtype, output_padding
)
batch, in_c, in_d, in_h, in_w = data_pad.shape
out_c, _, filter_d, filter_h, filter_w = kernel_transform.shape
stride_d, stride_h, stride_w = strides
# convolution stage
out_c = simplify(out_c)
out_d = simplify(in_d - filter_d + 1)
out_h = simplify(in_h - filter_h + 1)
out_w = simplify(in_w - filter_w + 1)
dc = te.reduce_axis((0, in_c), name="dc")
dd = te.reduce_axis((0, filter_d), name="dd")
dh = te.reduce_axis((0, filter_h), name="dh")
dw = te.reduce_axis((0, filter_w), name="dw")
Output = te.compute(
(batch, out_c, out_d, out_h, out_w),
lambda b, c, d, h, w: te.sum(
data_pad[b, dc, d + dd, h + dh, w + dw].astype(out_dtype)
* kernel_transform[c, dc, dd, dh, dw].astype(out_dtype),
axis=[dc, dd, dh, dw],
),
tag="conv3d_transpose_ncdhw",
)
return Output
def group_conv3d_transpose_ncdhw(data, kernel, strides, padding, out_dtype, output_padding, groups):
"""Transposed group 3D convolution ncdhw forward operator.
Parameters
----------
data : tvm.te.Tensor
5-D with shape [batch, in_channel, in_depth, in_height, in_width]
kernel : tvm.te.Tensor
5-D with shape [in_channel, num_filter, filter_depth, filter_height, filter_width]
strides : int or a list/tuple of three ints
The spatial stride along depth,height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
out_dtype : str
The output data type. This is used for mixed precision.
output_padding : tuple of ints
Used to get the right output shape for gradients
groups : int
number of groups
Returns
-------
Output : tvm.te.Tensor
5-D with shape [batch, out_channel, out_depth, out_height, out_width]
"""
if not isinstance(strides, tuple | list):
strides = (strides, strides, strides)
if groups == 1:
return conv3d_transpose_ncdhw(data, kernel, strides, padding, out_dtype, output_padding)
data_pad, kernel_transform = conv3d_transpose_ncdhw_preprocess(
data, kernel, strides, padding, out_dtype, output_padding
)
batch, in_c, in_d, in_h, in_w = data_pad.shape
out_c, _, filter_d, filter_h, filter_w = kernel_transform.shape
assert in_c % groups == 0, f"input channels {in_c} must divide group size {groups}"
# convolution stage
out_c = simplify(out_c * groups)
out_d = simplify(in_d - filter_d + 1)
out_h = simplify(in_h - filter_h + 1)
out_w = simplify(in_w - filter_w + 1)
dc = te.reduce_axis((0, in_c // groups), name="dc")
dd = te.reduce_axis((0, filter_d), name="dd")
dh = te.reduce_axis((0, filter_h), name="dh")
dw = te.reduce_axis((0, filter_w), name="dw")
# data: batch, in_channels, out_d, out_h, out_w
# weight: out_channels // G, in_channels, out_d, out_h, out_w
return te.compute(
(batch, out_c, out_d, out_h, out_w),
lambda b, c, d, h, w: te.sum(
data_pad[
b, c // (out_c // groups) * (in_c // groups) + dc, d + dd, h + dh, w + dw
].astype(out_dtype)
* kernel_transform[
c % (out_c // groups),
c // (out_c // groups) * (in_c // groups) + dc,
dd,
dh,
dw,
].astype(out_dtype),
axis=[dc, dd, dh, dw],
),
tag="group_conv3d_transpose_ncdhw",
)
+124
View File
@@ -0,0 +1,124 @@
# 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.
# ruff: noqa: E731
"""Correlation operators"""
from tvm import te
from ..utils import get_const_tuple
from .pad import pad
def correlation_nchw(
data1, data2, kernel_size, max_displacement, stride1, stride2, padding, is_multiply
):
"""Correlation operator in NCHW layout.
Parameters
----------
data1 : tvm.te.Tensor
4-D with shape [batch, channel, height, width]
data2 : tvm.te.Tensor
4-D with shape [batch, channel, height, width]
kernel_size: int
Kernel size for correlation, must be an odd number
max_displacement: int
Max displacement of Correlation
stride1: int
Stride for data1
stride2: int
Stride for data2 within the neightborhood centered around data1
padding : int or a list/tuple of 2 or 4 ints
Padding size, or
[pad_height, pad_width] for 2 ints, or
[pad_top, pad_left, pad_bottom, pad_right] for 4 ints
is_multiply: bool
operation type is either multiplication or substraction
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
# pylint: disable=unnecessary-lambda, invalid-name
data_shape = get_const_tuple(data1.shape)
assert get_const_tuple(data2.shape) == data_shape, "data1 and data2 should have the same shape"
assert kernel_size > 0 and kernel_size % 2, "kernel_size should be non-negative odd number"
if isinstance(padding, tuple | list):
if len(padding) == 2:
pad_before_h = pad_after_h = padding[0]
pad_before_w = pad_after_w = padding[1]
elif len(padding) == 4:
pad_before_h, pad_before_w, pad_after_h, pad_after_w = padding
else:
raise ValueError("invalid padding")
elif isinstance(padding, int):
pad_before_h = pad_after_h = pad_before_w = pad_after_w = padding
else:
raise ValueError("invalid padding")
pad_before = [0, 0, pad_before_h, pad_before_w]
pad_after = [0, 0, pad_after_h, pad_after_w]
padded_data1 = pad(data1, pad_before, pad_after)
padded_data2 = pad(data2, pad_before, pad_after)
batch, channel, height, width = data_shape
kernel_radius = (kernel_size - 1) // 2
border_size = max_displacement + kernel_radius
displacement_radius = max_displacement // stride2
displacement_size = 2 * displacement_radius + 1
padded_width = width + pad_before_w + pad_after_w
padded_height = height + pad_before_h + pad_after_h
out_channel = displacement_size * displacement_size
out_height = (padded_height - 2 * border_size + stride1 - 1) // stride1
out_width = (padded_width - 2 * border_size + stride1 - 1) // stride1
rc = te.reduce_axis((0, channel), name="rc")
ry = te.reduce_axis((0, kernel_size), name="ry")
rx = te.reduce_axis((0, kernel_size), name="rx")
if is_multiply:
corr_func = lambda x, y: x * y
else:
corr_func = lambda x, y: te.abs(x - y)
def _compute_correlation(n, q, i, j):
# location in data1
y1 = i * stride1 + max_displacement
x1 = j * stride1 + max_displacement
# location in data2
y2 = y1 + (te.indexdiv(q, displacement_size) - displacement_radius) * stride2
x2 = x1 + (te.indexmod(q, displacement_size) - displacement_radius) * stride2
return te.sum(
corr_func(padded_data1[n, rc, y1 + ry, x1 + rx], padded_data2[n, rc, y2 + ry, x2 + rx]),
axis=[rc, ry, rx],
)
correlation = te.compute(
(batch, out_channel, out_height, out_width),
lambda n, q, i, j: _compute_correlation(n, q, i, j),
tag="correlation_nchw",
)
return correlation / (kernel_size * kernel_size * channel)
+241
View File
@@ -0,0 +1,241 @@
# 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=invalid-name, too-many-locals, too-many-arguments
"""Deformable Conv2D operators"""
import tvm
from tvm import te
from ..cpp.utils import bilinear_sample_nchw, bilinear_sample_nhwc
from ..utils import get_const_tuple
from .utils import get_pad_tuple
def deformable_conv2d_nchw(
data, offset, kernel, strides, padding, dilation, deformable_groups, groups, out_dtype
):
"""Deformable conv2D operator in NCHW layout.
The deformable convolution operation is described in https://arxiv.org/abs/1703.06211
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, in_channel, in_height, in_width]
offset : tvm.te.Tensor
4-D with shape [batch, deformable_groups * filter_height * filter_width * 2,
out_height, out_width].
kernel : tvm.te.Tensor
4-D with shape [num_filter, in_channel, filter_height, filter_width]
strides : int or a list/tuple of two ints
stride size, or [stride_height, stride_width]
padding : int or a list/tuple of two ints
padding size, or [pad_height, pad_width]
dilation : int or a list/tuple of two ints
dilation size, or [dilation_height, dilation_width]
deformable_groups : int
number of deformable groups
groups : int
number of groups
Returns
-------
output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
if out_dtype is None:
out_dtype = data.dtype
if isinstance(strides, int):
stride_h = stride_w = strides
else:
stride_h, stride_w = strides
if isinstance(dilation, int):
dilation_h = dilation_w = dilation
else:
dilation_h, dilation_w = dilation
batch, in_channel, in_height, in_width = get_const_tuple(data.shape)
out_channel, channel, kernel_h, kernel_w = get_const_tuple(kernel.shape)
_, _, out_height, out_width = get_const_tuple(offset.shape)
assert in_channel % deformable_groups == 0, "Input cahnnels must divide deformable group size"
assert groups == 1, "deformable_conv2d_nchw does not support groups > 1"
ic_per_dgroup = channel // deformable_groups
dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
pad_top, pad_left, _, _ = get_pad_tuple(padding, (dilated_kernel_h, dilated_kernel_w))
rc = te.reduce_axis((0, in_channel), name="rc")
ry = te.reduce_axis((0, kernel_h), name="ry")
rx = te.reduce_axis((0, kernel_w), name="rx")
zero = tvm.tirx.const(0.0, data.dtype)
def _bilinear(n, c, h, w):
outside = tvm.tirx.any(h < 0, w < 0, h >= in_height, w >= in_width)
val = bilinear_sample_nchw(data, (n, c, h, w), in_height - 1, in_width - 1)
return tvm.tirx.if_then_else(outside, zero, val)
data_deform = te.compute(
(batch, in_channel, kernel_h, kernel_w, out_height, out_width),
lambda n, c, kh, kw, y, x: _bilinear(
n,
c,
y * stride_h
- pad_top
+ kh * dilation_h
+ offset[
n, c // ic_per_dgroup * (kernel_w * kernel_h * 2) + (kh * kernel_w + kw) * 2, y, x
],
x * stride_w
- pad_left
+ kw * dilation_w
+ offset[
n,
c // ic_per_dgroup * (kernel_w * kernel_h * 2) + (kh * kernel_w + kw) * 2 + 1,
y,
x,
],
),
tag="data_deform",
)
return te.compute(
(batch, out_channel, out_height, out_width),
lambda n, f, y, x: te.sum(
data_deform[n, rc, ry, rx, y, x].astype(out_dtype)
* kernel[f, rc, ry, rx].astype(out_dtype),
axis=[rc, ry, rx],
),
tag="deformable_conv2d_nchw",
)
def deformable_conv2d_nhwc(
data, offset, kernel, strides, padding, dilation, deformable_groups, groups, out_dtype
):
"""Deformable conv2D operator in NHWC layout.
The deformable convolution operation is described in https://arxiv.org/abs/1703.06211
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, in_height, in_width, in_channel]
offset : tvm.te.Tensor
4-D with shape [batch, out_height, out_width,
deformable_groups * filter_height * filter_width * 2].
kernel : tvm.te.Tensor
4-D with shape [filter_height, filter_width, in_channel, num_filter]
strides : int or a list/tuple of two ints
stride size, or [stride_height, stride_width]
padding : int or a list/tuple of two ints
padding size, or [pad_height, pad_width]
dilation : int or a list/tuple of two ints
dilation size, or [dilation_height, dilation_width]
deformable_groups : int
number of deformable groups
groups : int
number of groups
Returns
-------
output : tvm.te.Tensor
4-D with shape [batch, out_height, out_width, out_channel]
"""
if out_dtype is None:
out_dtype = data.dtype
if isinstance(strides, int):
stride_h = stride_w = strides
else:
stride_h, stride_w = strides
if isinstance(dilation, int):
dilation_h = dilation_w = dilation
else:
dilation_h, dilation_w = dilation
batch, in_height, in_width, in_channel = get_const_tuple(data.shape)
kernel_h, kernel_w, channel, out_channel = get_const_tuple(kernel.shape)
_, out_height, out_width, _ = get_const_tuple(offset.shape)
assert in_channel % deformable_groups == 0, "Input cahnnels must divide deformable group size"
assert groups == 1, "deformable_conv2d_nchw does not support groups > 1"
ic_per_dgroup = channel // deformable_groups
dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
pad_top, pad_left, _, _ = get_pad_tuple(padding, (dilated_kernel_h, dilated_kernel_w))
rc = te.reduce_axis((0, in_channel), name="rc")
ry = te.reduce_axis((0, kernel_h), name="ry")
rx = te.reduce_axis((0, kernel_w), name="rx")
zero = tvm.tirx.const(0.0, data.dtype)
def _bilinear(n, h, w, c):
outside = tvm.tirx.any(h < 0, w < 0, h >= in_height, w >= in_width)
val = bilinear_sample_nhwc(data, (n, h, w, c), in_height - 1, in_width - 1)
return tvm.tirx.if_then_else(outside, zero, val)
data_deform = te.compute(
(batch, kernel_h, kernel_w, in_channel, out_height, out_width),
lambda n, kh, kw, c, y, x: _bilinear(
n,
y * stride_h
- pad_top
+ kh * dilation_h
+ offset[
n, y, x, c // ic_per_dgroup * (kernel_w * kernel_h * 2) + (kh * kernel_w + kw) * 2
],
x * stride_w
- pad_left
+ kw * dilation_w
+ offset[
n,
y,
x,
c // ic_per_dgroup * (kernel_w * kernel_h * 2) + (kh * kernel_w + kw) * 2 + 1,
],
c,
),
tag="data_deform",
)
return te.compute(
(batch, out_height, out_width, out_channel),
lambda n, y, x, f: te.sum(
data_deform[n, ry, rx, rc, y, x].astype(out_dtype)
* kernel[ry, rx, rc, f].astype(out_dtype),
axis=[ry, rx, rc],
),
tag="deformable_conv2d_nhwc",
)
+261
View File
@@ -0,0 +1,261 @@
# 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=invalid-name,unused-argument
# ruff: noqa: E741, F821
"""TVM operator fully connected compute."""
import tvm
from tvm import te
from .. import add, tag
def matmul(
tensor_a,
tensor_b,
bias=None,
out_dtype=None,
transpose_a=False,
transpose_b=False,
auto_scheduler_rewritten_layout="",
meta_schedule_original_shape=None,
):
"""The default implementation of matmul in topi.
Parameters
----------
tensor_a : tvm.te.Tensor
2-D with shape [batch, in_dim]
tensor_b : tvm.te.Tensor
2-D with shape [out_dim, in_dim]
bias : Optional[tvm.te.Tensor]
1-D with shape [out_dim]
out_dtype : Optional[str]
The output type. This is used for mixed precision.
transpose_a : Optional[bool] = False
Whether the tensor_a is in transposed format.
transpose_b : Optional[bool] = False
Whether the tensor_b is in transposed format.
auto_scheduler_rewritten_layout: Optional[str] = ""
The layout after auto-scheduler's layout rewrite pass.
meta_schedule_original_shape: Optional[List[Expr]] = None
The original shape of the input tensor.
Returns
-------
output : tvm.te.Tensor
2-D with shape [batch, out_dim]
"""
# TODO(yixin): support cases for 1-dim input
# TODO(yixin): adding support and further check for >2-dim input in autotvm template
assert len(tensor_a.shape) >= 2 and len(tensor_b.shape) >= 2, (
"1-dim matmul is not supported yet."
)
if bias is not None:
assert len(bias.shape) == 1
if out_dtype is None:
out_dtype = tensor_a.dtype
if transpose_a:
reduce_dim_a, in_dim = tensor_a.shape[-2:]
else:
in_dim, reduce_dim_a = tensor_a.shape[-2:]
batch_dims_a = tensor_a.shape[:-2]
if auto_scheduler_rewritten_layout:
# Infer shape for the rewritten layout
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
if meta_schedule_original_shape:
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
if transpose_b:
out_dim, reduce_dim_b = tensor_b.shape[-2:]
else:
reduce_dim_b, out_dim = tensor_b.shape[-2:]
batch_dims_b = tensor_b.shape[:-2]
if not isinstance(reduce_dim_a, tvm.tirx.Var) and not isinstance(reduce_dim_b, tvm.tirx.Var):
assert int(reduce_dim_a) == int(reduce_dim_b), (
f"Reduction dimensions of dense do not match. {reduce_dim_a} vs {reduce_dim_b}."
)
result_ndim = max(len(batch_dims_a), len(batch_dims_b))
batch_dims_a = [1] * (result_ndim - len(batch_dims_a)) + batch_dims_a
batch_dims_b = [1] * (result_ndim - len(batch_dims_b)) + batch_dims_b
for idx, (l, r) in enumerate(zip(batch_dims_a, batch_dims_b)):
if (
not isinstance(l, tvm.tirx.Var)
and not isinstance(r, tvm.tirx.Var)
and int(l) != 1
and int(r) != 1
):
assert int(l) == int(r), (
"Batch dimensions of dense do not match: "
f"{tensor_a.shape[:-2]} vs {tensor_b.shape[:-2]}."
)
if not isinstance(l, tvm.tirx.Var) and int(l) == 1:
batch_dims_a[idx] = batch_dims_b[idx]
k = te.reduce_axis((0, reduce_dim_a), name="k")
def compute(*indices):
batch_indices_a = indices[-len(tensor_a.shape) : -2]
batch_indices_a = [
i if isinstance(dim, tvm.tirx.Var) or int(dim) != 1 else 0
for i, dim in zip(batch_indices_a, tensor_a.shape[:-2])
]
batch_indices_b = indices[-len(tensor_b.shape) : -2]
batch_indices_b = [
i if isinstance(dim, tvm.tirx.Var) or int(dim) != 1 else 0
for i, dim in zip(batch_indices_b, tensor_b.shape[:-2])
]
i, j = indices[-2:]
a_indices = (*batch_indices_a, k, i) if transpose_a else (*batch_indices_a, i, k)
b_indices = (*batch_indices_b, j, k) if transpose_b else (*batch_indices_b, k, j)
return te.sum(
tensor_a[a_indices].astype(out_dtype) * tensor_b[b_indices].astype(out_dtype), axis=k
)
compute_name = {
(True, True): "T_matmul_TT",
(True, False): "T_matmul_TN",
(False, True): "T_matmul_NT",
(False, False): "T_matmul_NN",
}[(transpose_a, transpose_b)]
# TODO(jcf94): Remove `dense` when `matmul` is finally ready
compute_tag = "dense" if (transpose_a, transpose_b) == (False, True) else "matmul"
mat = te.compute(
(*batch_dims_a, in_dim, out_dim),
compute,
name=compute_name,
tag=compute_tag,
attrs={"layout_free_placeholders": [tensor_b]},
)
if bias is not None:
mat = add(mat, bias.astype(out_dtype))
if auto_scheduler_rewritten_layout:
raise RuntimeError("LEGACY-FLOW triggered, to be removed")
return mat
def dense(
data,
weight,
bias=None,
out_dtype=None,
auto_scheduler_rewritten_layout="",
meta_schedule_original_shape=None,
):
"""The default implementation of dense in topi.
This is an alias of matmul_nt operator for data tensor in non-transposed format and weight
tensor in transposed format.
Parameters
----------
data : tvm.te.Tensor
2-D with shape [batch, in_dim]
weight : tvm.te.Tensor
2-D with shape [out_dim, in_dim]
bias : Optional[tvm.te.Tensor]
1-D with shape [out_dim]
out_dtype : Optional[str]
The output type. This is used for mixed precision.
auto_scheduler_rewritten_layout: str = ""
The layout after auto-scheduler's layout rewrite pass.
meta_schedule_original_shape: Optional[List[Expr]] = None
The original shape of the input tensor.
Returns
-------
output : tvm.te.Tensor
2-D with shape [batch, out_dim]
"""
return matmul(
data,
weight,
bias,
out_dtype,
False,
True,
auto_scheduler_rewritten_layout,
meta_schedule_original_shape,
)
def dense_pack(data, weight, bias=None, out_dtype=None):
"""The default implementation of dense_pack in topi.
Parameters
----------
data : tvm.te.Tensor
2-D with shape [batch, in_dim]
weight : tvm.te.Tensor
2-D with shape [out_dim, in_dim]
bias : Optional[tvm.te.Tensor]
1-D with shape [out_dim]
out_dtype : Optional[str]
The output type. This is used for mixed precision.
Returns
-------
output : tvm.te.Tensor
2-D with shape [batch, out_dim]
"""
if out_dtype is None:
out_dtype = data.dtype
M, K = get_const_tuple(data.shape) # batch, in_dim
N, _, packw_bn = get_const_tuple(weight.shape) # out_dim
N = N * packw_bn
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
k = te.reduce_axis((0, K), name="k")
C = te.compute(
(M, N),
lambda y, x: te.sum(
data[y, k].astype(out_dtype)
* weight[idxdiv(x, packw_bn), k, idxmod(x, packw_bn)].astype(out_dtype),
axis=k,
),
name="T_dense_pack",
tag="dense_pack",
)
if bias is not None:
C = te.compute((M, N), lambda i, j: C[i, j] + bias[j].astype(out_dtype), tag=tag.BROADCAST)
return C
+88
View File
@@ -0,0 +1,88 @@
# 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=invalid-name
"""TVM operator depth_to_space compute."""
import tvm
from tvm import te
from .. import tag
def depth_to_space(data, block_size, layout="NCHW", mode="DCR"):
"""Perform depth to space transformation on the data
Parameters
----------
data : tvm.te.Tensor
4-D tensor in either NCHW or NHWC layout.
block_size : int
Size of blocks to compose from channel dimension.
layout : string
Either NCHW or NHWC, indicating data layout.
mode : string
Either DCR or CDR, indicates how channels should be accessed.
In DCR, channels are interwoven in the Tensorflow style while
in CDR channels are accessed sequentially as in Pytorch.
Returns
-------
output : tvm.te.Tensor
Output of shape [N, C / block_size**2, H * block_size, W * block_size]
"""
if layout == "NCHW":
in_n, in_c, in_h, in_w = data.shape
channel_factor = tvm.tirx.truncdiv(in_c, (block_size * block_size))
output_shape = [in_n, channel_factor, in_h * block_size, in_w * block_size]
elif layout == "NHWC":
in_n, in_h, in_w, in_c = data.shape
channel_factor = tvm.tirx.truncdiv(in_c, (block_size * block_size))
output_shape = [in_n, in_h * block_size, in_w * block_size, channel_factor]
else:
raise ValueError("Only NCHW and NHWC layouts are currently supported.")
def _get_indices(*indices):
if layout == "NCHW":
n, c, y, x = indices
elif layout == "NHWC":
n, y, x, c = indices
return n, c, y, x
def _get_pixel(n, c, y, x):
block_x = tvm.tirx.truncdiv(x, block_size)
block_y = tvm.tirx.truncdiv(y, block_size)
idx_x = tvm.tirx.truncmod(x, block_size)
idx_y = tvm.tirx.truncmod(y, block_size)
if mode == "DCR":
channel_idx = channel_factor * ((block_size * idx_y) + idx_x) + c
else:
channel_idx = (c * block_size * block_size) + ((block_size * idx_y) + idx_x)
if layout == "NCHW":
output = data(n, channel_idx, block_y, block_x)
else:
output = data(n, block_y, block_x, channel_idx)
return output
def _compute(*indices):
n, c, y, x = _get_indices(*indices)
return _get_pixel(n, c, y, x)
return te.compute(output_shape, _compute, name="depth_to_space", tag=tag.INJECTIVE)
+462
View File
@@ -0,0 +1,462 @@
# 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=invalid-name, unused-variable, too-many-locals, unused-argument
# ruff: noqa: F841
"""Depthwise convolution operators"""
from collections import namedtuple
import numpy as np
import tvm
from tvm import te
from ..utils import get_const_tuple, simplify
from .dilate import dilate
from .pad import pad
from .utils import get_pad_tuple
# workload description of depthwise-conv2d
Workload = namedtuple(
"Workload",
[
"in_dtype",
"out_dtype",
"height",
"width",
"in_filter",
"out_filter",
"kernel_h",
"kernel_w",
"padt",
"padl",
"padb",
"padr",
"dilation_h",
"dilation_w",
"stride_h",
"stride_w",
],
)
def _get_workload(data, kernel, stride, padding, dilation, out_dtype, data_layout="NCHW"):
"""Get the workload structure for a depthwise conv2d.
Input data and filter should use NCHW layout.
"""
if data_layout == "NCHW":
_, in_channel, height, width = get_const_tuple(data.shape)
filter_channel, channel_multiplier, kh, kw = get_const_tuple(kernel.shape)
elif data_layout == "NHWC":
_, height, width, in_channel = get_const_tuple(data.shape)
kh, kw, filter_channel, channel_multiplier = get_const_tuple(kernel.shape)
elif data_layout == "NCHWc":
_, in_channel_chunk, height, width, in_channel_block = get_const_tuple(data.shape)
in_channel = in_channel_chunk * in_channel_block
(filter_channel_chunk, cm_chunk, kh, kw, cm_block, filter_channel_block) = get_const_tuple(
kernel.shape
)
filter_channel = filter_channel_chunk * filter_channel_block
channel_multiplier = cm_chunk * cm_block
assert in_channel_block == filter_channel_block, (
f"Incorrect dimensions, data has block size {in_channel_block}, but filter has "
f"block size {filter_channel_block}"
)
else:
raise ValueError(f"Data layout {data_layout} not supported")
assert in_channel == filter_channel, (
f"Incorrect dimensions, data has {in_channel} channels but filter expects "
f"{filter_channel} channels"
)
out_channel = filter_channel * channel_multiplier
dilation_h, dilation_w = (
dilation if isinstance(dilation, tuple | list) else (dilation, dilation)
)
if isinstance(stride, tuple | list):
HSTR, WSTR = stride
else:
HSTR, WSTR = stride, stride
assert (data.dtype == kernel.dtype) or (data.dtype == "uint8" and kernel.dtype == "int8"), (
f"Do not support inputs with different data types now. {data.dtype} vs. {kernel.dtype}"
)
dilated_kernel_h = (kh - 1) * dilation_h + 1
dilated_kernel_w = (kw - 1) * dilation_w + 1
pt, pl, pb, pr = get_pad_tuple(padding, (dilated_kernel_h, dilated_kernel_w))
return Workload(
data.dtype,
out_dtype,
height,
width,
in_channel,
out_channel,
kh,
kw,
pt,
pl,
pb,
pr,
dilation_h,
dilation_w,
HSTR,
WSTR,
)
def depthwise_conv2d_nchw(Input, Filter, stride, padding, dilation, out_dtype=None):
"""Depthwise convolution nchw forward operator.
Parameters
----------
Input : tvm.te.Tensor
4-D with shape [batch, in_channel, in_height, in_width]
Filter : tvm.te.Tensor
4-D with shape [in_channel, channel_multiplier, filter_height, filter_width]
stride : int or a list/tuple of two ints
The spatial stride, or (stride_height, stride_width).
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation: int or a list/tuple of two ints
dilation size, or [dilation_height, dilation_width]
out_dtype: str, optional
Output data type
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, out_channel, out_height, out_width]
"""
out_dtype = Input.dtype if out_dtype is None else out_dtype
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
if isinstance(dilation, int):
dilation_h = dilation_w = dilation
else:
dilation_h, dilation_w = dilation
batch, in_channel, in_height, in_width = Input.shape
# shape of dilated kernel
filter_channel, channel_multiplier, filter_height, filter_width = Filter.shape
dilated_kernel_h = (filter_height - 1) * dilation_h + 1
dilated_kernel_w = (filter_width - 1) * dilation_w + 1
pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
padding, (dilated_kernel_h, dilated_kernel_w)
)
out_channel = simplify(in_channel * channel_multiplier)
out_height = simplify((in_height - dilated_kernel_h + pad_top + pad_down) // stride_h + 1)
out_width = simplify((in_width - dilated_kernel_w + pad_left + pad_right) // stride_w + 1)
# padding stage
pad_before = [0, 0, pad_top, pad_left]
pad_after = [0, 0, pad_down, pad_right]
PaddedInput = pad(Input, pad_before, pad_after, name="PaddedInput")
# depthconv stage
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
di = te.reduce_axis((0, filter_height), name="di")
dj = te.reduce_axis((0, filter_width), name="dj")
Output = te.compute(
(batch, out_channel, out_height, out_width),
lambda b, c, i, j: te.sum(
(
PaddedInput[
b,
idxdiv(c, channel_multiplier),
i * stride_h + di * dilation_h,
j * stride_w + dj * dilation_w,
].astype(out_dtype)
* Filter[
idxdiv(c, channel_multiplier), idxmod(c, channel_multiplier), di, dj
].astype(out_dtype)
),
axis=[di, dj],
),
name="DepthwiseConv2d",
tag="depthwise_conv2d_nchw",
)
return Output
def depthwise_conv2d_nhwc(
Input, Filter, stride, padding, dilation, kernel_layout="HWOI", out_dtype=None
):
"""Depthwise convolution nhwc forward operator.
Parameters
----------
Input : tvm.te.Tensor
4-D with shape [batch, in_height, in_width, in_channel]
Filter : tvm.te.Tensor
4-D with shape [filter_height, filter_width, in_channel, channel_multiplier]
stride : tuple of two ints
The spatial stride along height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation: int or a list/tuple of two ints
dilation size, or [dilation_height, dilation_width]
out_dtype: str, optional
Output data type
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, out_height, out_width, out_channel]
"""
out_dtype = Input.dtype if out_dtype is None else out_dtype
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
if isinstance(dilation, int):
dilation_h = dilation_w = dilation
else:
dilation_h, dilation_w = dilation
batch, in_height, in_width, in_channel = Input.shape
# shape of dilated kernel
if kernel_layout == "HWIO":
filter_height, filter_width, channel_multiplier, filter_channel = Filter.shape
kernel_permutation = [0, 1, 3, 2]
else:
filter_height, filter_width, filter_channel, channel_multiplier = Filter.shape
kernel_permutation = [0, 1, 2, 3]
dilated_kernel_h = (filter_height - 1) * dilation_h + 1
dilated_kernel_w = (filter_width - 1) * dilation_w + 1
pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
padding, (dilated_kernel_h, dilated_kernel_w)
)
out_channel = simplify(in_channel * channel_multiplier)
out_height = simplify((in_height - dilated_kernel_h + pad_top + pad_down) // stride_h + 1)
out_width = simplify((in_width - dilated_kernel_w + pad_left + pad_right) // stride_w + 1)
# padding stage
pad_before = [0, pad_top, pad_left, 0]
pad_after = [0, pad_down, pad_right, 0]
PaddedInput = pad(Input, pad_before, pad_after, name="PaddedInput")
# depthconv stage
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
di = te.reduce_axis((0, filter_height), name="di")
dj = te.reduce_axis((0, filter_width), name="dj")
Output = te.compute(
(batch, out_height, out_width, out_channel),
lambda b, i, j, c: te.sum(
(
PaddedInput[
b,
i * stride_h + di * dilation_h,
j * stride_w + dj * dilation_w,
idxdiv(c, channel_multiplier),
].astype(out_dtype)
* Filter[
tuple(
np.array(
[di, dj, idxdiv(c, channel_multiplier), idxmod(c, channel_multiplier)]
)[kernel_permutation]
)
].astype(out_dtype)
),
axis=[di, dj],
),
name="DepthwiseConv2d",
tag="depthwise_conv2d_nhwc",
)
return Output
def depthwise_conv2d_backward_input_nhwc(Filter, Out_grad, oshape, ishape, stride, padding):
"""Depthwise convolution nhwc backward wrt input operator.
Parameters
----------
Filter : tvm.te.Tensor
4-D with shape [filter_height, filter_width, in_channel, channel_multiplier]
Out_grad : tvm.te.Tensor
4-D with shape [batch, out_height, out_width, out_channel]
stride : tuple of two ints
The spatial stride along height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
Returns
-------
Output : tvm.te.Tensor
4-D with shape [batch, in_height, in_width, in_channel]
"""
batch, in_h, in_w, in_c = ishape
_, out_h, out_w, out_c = oshape
filter_h, filter_w, _, channel_multiplier = Filter.shape
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
dilated_out_grad = dilate(Out_grad, [1, stride_h, stride_w, 1], name="dilated_out_grad")
# padding params in forward propagation
fpad_top, fpad_left, fpad_bottom, fpad_right = get_pad_tuple(padding, (filter_h, filter_w))
# padding params in backward propagation
bpad_top = filter_h - 1 - fpad_top
bpad_bottom = (filter_h - 1 - fpad_bottom) + (stride_h - 1)
bpad_left = filter_w - 1 - fpad_left
bpad_right = (filter_w - 1 - fpad_right) + (stride_w - 1)
padded_out_grad = pad(
dilated_out_grad,
[0, bpad_top, bpad_left, 0],
[0, bpad_bottom, bpad_right, 0],
name="padded_out_grad",
)
dh = te.reduce_axis((0, filter_h), name="dh")
dw = te.reduce_axis((0, filter_w), name="dw")
dc = te.reduce_axis((0, channel_multiplier), name="dc")
In_grad = te.compute(
(batch, in_h, in_w, in_c),
lambda b, h, w, c: te.sum(
padded_out_grad[b, h + dh, w + dw, c * channel_multiplier + dc]
* Filter[filter_h - 1 - dh, filter_w - 1 - dw, c, dc],
axis=[dh, dw, dc],
),
tag="depthwise_conv2d_backward_input_nhwc",
)
return In_grad
def depthwise_conv2d_backward_weight_nhwc(Input, Out_grad, oshape, fshape, stride, padding):
"""Depthwise convolution nhwc backward wrt weight operator.
Parameters
----------
Input : tvm.te.Tensor
4-D with shape [batch, in_height, in_width, in_channel]
Out_grad : tvm.te.Tensor
4-D with shape [batch, out_height, out_width, out_channel]
stride : tuple of two ints
The spatial stride along height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
Returns
-------
Output : tvm.te.Tensor
4-D with shape [filter_height, filter_width, in_channel, channel_multiplier]
"""
batch, out_h, out_w, out_c = oshape
filter_h, filter_w, _, channel_multiplier = fshape
in_c = Input.shape[3].value
if isinstance(stride, int):
stride_h = stride_w = stride
else:
stride_h, stride_w = stride
pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(padding, (filter_h, filter_w))
padded_in = pad(
Input, [0, pad_top, pad_left, 0], [0, pad_bottom, pad_right, 0], name="padded_in"
)
dh = te.reduce_axis((0, Out_grad.shape[1].value), name="dh")
dw = te.reduce_axis((0, Out_grad.shape[2].value), name="dw")
db = te.reduce_axis((0, batch), name="db")
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
Weight_grad = te.compute(
(filter_h, filter_w, in_c, channel_multiplier),
lambda fh, fw, c, m: te.sum(
Out_grad[db, dh, dw, c * channel_multiplier + idxmod(m, channel_multiplier)]
* padded_in[db, fh + dh * stride_h, fw + dw * stride_w, c],
axis=[db, dh, dw],
),
tag="depthwise_conv2d_backward_weight_nhwc",
)
return Weight_grad
def depthwise_conv2d_NCHWc(
Input, Filter, stride, padding, dilation, layout, out_layout, out_dtype=None
):
"""Depthwise convolution NCHW[x]c forward operator.
Parameters
----------
Input : tvm.te.Tensor
5-D with shape [batch, in_channel_chunk, in_height, in_width, in_channel_block]
Filter : tvm.te.Tensor
6-D with shape [out_channel_chunk, 1, filter_height, filter_width, 1, out_channel_block]
In NCHWc depthwise convolution,
we group kernel's in_channel and channel_multiplier together then do the tiling.
stride : tuple of two ints
The spatial stride along height and width
padding : int or str
Padding size, or ['VALID', 'SAME']
dilation: int or a list/tuple of two ints
dilation size, or [dilation_height, dilation_width]
layout : str
Input data layout
out_layout : str
Output data layout
out_dtype: str, optional
Output data type
Returns
-------
Output : tvm.te.Tensor
5-D with shape [batch, out_channel_chunk, out_height, out_width, out_channel_block]
"""
raise ValueError("missing register for topi.nn.depthwise_conv2d_NCHWc")
+73
View File
@@ -0,0 +1,73 @@
# 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=invalid-name
"""Dilation operators"""
import tvm
from tvm import te
from .. import tag, utils
@te.tag_scope(tag=tag.INJECTIVE + ",dilate")
def dilate(data, strides, dilation_value=0.0, name="DilatedInput"):
"""Dilate data with given dilation value (0 by default).
Parameters
----------
data : tvm.te.Tensor
n-D, can be any layout.
strides : list / tuple of n ints
Dilation stride on each dimension, 1 means no dilation.
dilation_value : int/float, optional
Value used to dilate the input.
name : str, optional
The name prefix operators generated
Returns
-------
Output : tvm.te.Tensor
n-D, the same layout as data.
"""
n = len(data.shape)
if len(strides) != n:
raise ValueError(f"data dimension and strides size dismatch : {n} vs {len(strides)}")
ana = tvm.arith.Analyzer()
out_shape = tuple(ana.simplify((data.shape[i] - 1) * strides[i] + 1) for i in range(n))
def _dilate(*indices):
not_zero = []
index_tuple = []
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
for i in range(n):
if not utils.equal_const_int(strides[i], 1):
index_tuple.append(idxdiv(indices[i], strides[i]))
not_zero.append(idxmod(indices[i], strides[i]).equal(0))
else:
index_tuple.append(indices[i])
if not_zero:
not_zero = tvm.tirx.all(*not_zero)
return tvm.tirx.if_then_else(
not_zero, data(*index_tuple), tvm.tirx.const(dilation_value, data.dtype)
)
return data(*index_tuple)
return te.compute(out_shape, _dilate, name=name)
+143
View File
@@ -0,0 +1,143 @@
# 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.
"""Elementwise operators"""
import tvm
from tvm import te
from .. import tag
from ..utils import get_const_int
@tvm.te.tag_scope(tag=tag.ELEMWISE)
def relu(x):
"""Take relu of input x.
Parameters
----------
x : tvm.te.Tensor
Input argument.
Returns
-------
y : tvm.te.Tensor
The result.
"""
return te.compute(x.shape, lambda *i: tvm.te.max(x(*i), tvm.tirx.const(0, x.dtype)))
@tvm.te.tag_scope(tag=tag.ELEMWISE)
def leaky_relu(x, alpha):
"""Take leaky relu of input x.
Parameters
----------
x : tvm.te.Tensor
Input argument.
alpha : float
The slope for the small gradient when x < 0
Returns
-------
y : tvm.te.Tensor
The result.
"""
def _compute(*indices):
value = x(*indices)
calpha = tvm.tirx.const(alpha, value.ty)
return tvm.tirx.Select(value > 0, value, value * calpha)
return te.compute(x.shape, _compute)
@tvm.te.tag_scope(tag=tag.ELEMWISE)
def softplus(x, beta=1.0, threshold=20.0):
"""Compute Softplus activation for input x with numerical stability.
Parameters
----------
x : tvm.te.Tensor
Input tensor.
beta : float, optional
The scaling factor β in the Softplus formula (default is 1.0).
threshold : float, optional
The threshold value for numerical stability (default is 20.0).
Returns
-------
y : tvm.te.Tensor
The result.
"""
def _compute(*indices):
value = x(*indices)
b = tvm.tirx.const(beta, value.ty)
t = tvm.tirx.const(threshold, value.ty)
return tvm.tirx.Select(
b * value > t, value, (1 / b) * tvm.tirx.log(1 + tvm.tirx.exp(b * value))
)
return te.compute(x.shape, _compute)
@tvm.te.tag_scope(tag=tag.BROADCAST)
def prelu(x, slope, axis=1):
"""PReLU.
It accepts two arguments: an input ``x`` and a weight array ``W``
and computes the output as :math:`PReLU(x) y = x > 0 ? x : W * x`,
where :math:`*` is an elementwise multiplication for each sample in the
batch.
Parameters
----------
x : tvm.te.Tensor
Input argument.
slope : tvm.te.Tensor
Channelised slope tensor for prelu
axis : int
The axis where the channel data needs to be applied
Returns
-------
y : tvm.te.Tensor
The result.
Links
-----
[http://arxiv.org/pdf/1502.01852v1.pdf]
"""
assert len(slope.shape) == 1
assert axis < len(x.shape)
if slope.shape[0] == 1:
slope = te.compute(
(get_const_int(x.shape[axis]),), lambda c: slope[0], name="slope_broadcasted"
)
assert get_const_int(slope.shape[0]) == get_const_int(x.shape[axis])
def _compute_channelwise(*indices):
xval = x(*indices)
return tvm.tirx.Select(xval > 0, xval, xval * slope(indices[axis]))
return te.compute(x.shape, _compute_channelwise)
+188
View File
@@ -0,0 +1,188 @@
# 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.
# ruff: noqa: E741
"""FIFO buffer op"""
import tvm
from tvm import te
from .. import tag
from ..transform import concatenate, strided_slice
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",fifo_buffer")
def fifo_buffer(data, buffer, axis):
"""
FIFO buffer to enable computation reuse in CNNs with sliding indow input
Compute equivalent of
.. code-block:: python
concat(buffer, data, axis=axis)
.slice_axis(axis=axis,
begin=data.shape[axis],
end=data.shape[axis]+buffer.shape[axis])
Useful for
* Encoding explicit re-use of computation in convolution ops operated on a sliding window input
* Implementing a FIFO queue to cache intermediate results, e.g. as in Fast WaveNet.
Parameters
----------
data : tvm.te.Tensor
The input data
buffer : tvm.te.Tensor
Previous value of the FIFO buffer
axis : int
Specify which axis should be used for buffering
Returns
-------
result : tvm.te.Tensor
Updated value for the buffer
"""
assert len(data.shape) == len(buffer.shape), (
f"buffer and data must have same number of dimensions, "
f"buffer.shape = {buffer.shape}, data.shape = {data.shape}"
)
assert len(buffer.shape) >= 1, "Zero-dimension tensor not supported"
assert 0 <= axis < len(buffer.shape), "buffer axis out of range"
for i in range(len(data.shape)):
if i == axis:
assert int(str(data.shape[i])) <= int(str(buffer.shape[i]))
else:
assert int(str(data.shape[i])) == int(str(buffer.shape[i]))
buflen = buffer.shape[axis]
data_size = data.shape[axis]
# Explicitly write out formula up to 4D, and then use concat+slice combo for 5D and higher
if len(buffer.shape) == 1:
return te.compute(
buffer.shape,
lambda i: tvm.tirx.if_then_else(
i < buflen - data_size, buffer[i + data_size], data[i - buflen + data_size]
),
name="new_buffer",
)
if len(buffer.shape) == 2:
if axis == 0:
return te.compute(
buffer.shape,
lambda i, j: tvm.tirx.if_then_else(
i < buflen - data_size,
buffer[i + data_size, j],
data[i - buflen + data_size, j],
),
name="new_buffer",
)
if axis == 1:
return te.compute(
buffer.shape,
lambda i, j: tvm.tirx.if_then_else(
j < buflen - data_size,
buffer[i, j + data_size],
data[i, j - buflen + data_size],
),
name="new_buffer",
)
assert False, f"Invalid value for axis; it should be at most {len(buffer.shape)}"
elif len(buffer.shape) == 3:
if axis == 0:
return te.compute(
buffer.shape,
lambda i, j, k: tvm.tirx.if_then_else(
i < buflen - data_size,
buffer[i + data_size, j, k],
data[i - buflen + data_size, j, k],
),
name="new_buffer",
)
if axis == 1:
return te.compute(
buffer.shape,
lambda i, j, k: tvm.tirx.if_then_else(
j < buflen - data_size,
buffer[i, j + data_size, k],
data[i, j - buflen + data_size, k],
),
name="new_buffer",
)
if axis == 2:
return te.compute(
buffer.shape,
lambda i, j, k: tvm.tirx.if_then_else(
k < buflen - data_size,
buffer[i, j, k + data_size],
data[i, j, k - buflen + data_size],
),
name="new_buffer",
)
assert False, f"Invalid value for axis; it should be at most {len(buffer.shape)}"
elif len(buffer.shape) == 4:
if axis == 0:
return te.compute(
buffer.shape,
lambda i, j, k, l: tvm.tirx.if_then_else(
i < buflen - data_size,
buffer[i + data_size, j, k, l],
data[i - buflen + data_size, j, k, l],
),
name="new_buffer",
)
if axis == 1:
return te.compute(
buffer.shape,
lambda i, j, k, l: tvm.tirx.if_then_else(
j < buflen - data_size,
buffer[i, j + data_size, k, l],
data[i, j - buflen + data_size, k, l],
),
name="new_buffer",
)
if axis == 2:
return te.compute(
buffer.shape,
lambda i, j, k, l: tvm.tirx.if_then_else(
k < buflen - data_size,
buffer[i, j, k + data_size, l],
data[i, j, k - buflen + data_size, l],
),
name="new_buffer",
)
if axis == 3:
return te.compute(
buffer.shape,
lambda i, j, k, l: tvm.tirx.if_then_else(
l < buflen - data_size,
buffer[i, j, k, l + data_size],
data[i, j, k, l - buflen + data_size],
),
name="new_buffer",
)
assert False, f"Invalid value for axis; it should be at most {len(buffer.shape)}"
else:
# Implement FIFO buffer as combination of concat and slice
begin = [0] * len(buffer.shape)
begin[axis] = data.shape[axis]
end = list(buffer.shape[:])
end[axis] += data.shape[axis]
return strided_slice(concatenate((buffer, data), axis=axis), begin=begin, end=end)
return None
+54
View File
@@ -0,0 +1,54 @@
# 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.
"""TVM operator flatten compute."""
import tvm
from tvm import te
from .. import tag
@tvm.te.tag_scope(tag=tag.INJECTIVE)
def flatten(data):
"""Flattens the input array into a 2-D array by collapsing the higher dimensions.
Parameters
----------
data : tvm.te.Tensor
Input array.
Returns
-------
output : tvm.te.Tensor
2-D array with collapsed higher dimensions.
"""
ishape = data.shape
dim = 1
for i in range(1, len(ishape)):
dim = dim * ishape[i]
oshape = [ishape[0], dim]
idxdiv = tvm.tirx.indexdiv
idxmod = tvm.tirx.indexmod
def unwrap(idx, shape):
index = []
for s in reversed(shape):
index.append(idxmod(idx, s))
idx = idxdiv(idx, s)
return list(reversed(index))
return te.compute(oshape, lambda i, j: data(i, *unwrap(j, ishape[1:])))
+55
View File
@@ -0,0 +1,55 @@
# 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.
"""Layer normalization operator."""
from .. import cpp
def group_norm(data, gamma, beta, num_groups, channel_axis, axes, epsilon=1e-5):
"""Group normalization operator.
It accepts fp16 and fp32 as input data type. It will cast the input to fp32
to perform the computation. The output will have the same data type as input.
Parameters
----------
data : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
gamma: tvm.te.Tensor
1-D with shape (r_0) where r_0 == d_{channel_axis}
beta: tvm.te.Tensor
Optional, 1-D with shape (r_0) where r_0 == d_{channel_axis}
num_groups : int
The number of groups
channel_axis : int
The channel axis
axes : list of int
Axis over the normalization applied, excluding the channel axis
epsilon : float
The epsilon value to avoid division by zero.
Returns
-------
result : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
"""
return cpp.nn.group_norm(data, gamma, beta, num_groups, channel_axis, axes, epsilon)
+48
View File
@@ -0,0 +1,48 @@
# 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.
"""Instance normalization operator."""
from .. import cpp
def instance_norm(data, gamma, beta, channel_axis, axis, epsilon=1e-5):
"""Instance normalization operator.
Parameters
----------
data : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
gamma: tvm.te.Tensor
K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and d_{axis_k} == r_k
beta: tvm.te.Tensor
Optional, K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and d_{axis_k} == r_k
axis : list of int
Axis over the normalization applied (the axis along which the mean and variance are
computed)
epsilon : float
The epsilon value to avoid division by zero.
Returns
-------
result : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
"""
return cpp.nn.instance_norm(data, gamma, beta, channel_axis, axis, epsilon)
+49
View File
@@ -0,0 +1,49 @@
# 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.
"""Layer normalization operator."""
from .. import cpp
def layer_norm(data, gamma, beta, axis, epsilon=1e-5):
"""Layer normalization operator.
It accepts fp16 and fp32 as input data type. It will cast the input to fp32
to perform the computation. The output will have the same data type as input.
Parameters
----------
data : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
gamma: tvm.te.Tensor
K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and d_{axis_k} == r_k
beta: tvm.te.Tensor
Optional, K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and d_{axis_k} == r_k
axis : list of int
Axis over the normalization applied
epsilon : float
The epsilon value to avoid division by zero.
Returns
-------
result : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
"""
return cpp.nn.layer_norm(data, gamma, beta, axis, epsilon)
+59
View File
@@ -0,0 +1,59 @@
# 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=invalid-name
"""TVM operator for local response norm compute."""
from .. import cpp
def lrn(data, size, axis=1, alpha=0.0001, beta=0.75, bias=2):
"""Perform the across channels local response normalisation
on the input data.
sum_sqr_up^i{x, y} = (bias+((alpha/size)* \
{sum_{j=max(0, i-size/2)}^{min(N-1,i+size/2)} \
(data^j{x,y})^2}))^beta
output^i{x, y} = data^i{x, y}/sum_sqr_up^i{x, y}
N is the number for input channels
Parameters
----------
data : tvm.te.Tensor
4-D with shape [batch, channel, height, width]
size : int
normalisation window size
axis : int
input data layout channel axis
default value is 1 for NCHW format
bias : float
offset to avoid dividing by 0
alpha : float
to be divided
beta : float
exponent
Returns
-------
output : tvm.te.Tensor
4-D output with same shape
"""
return cpp.nn.lrn(data, size, axis, alpha, beta, bias)
+60
View File
@@ -0,0 +1,60 @@
# 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=invalid-name,unused-argument
"""Loss functions definitions."""
from . import cpp
def nll_loss(predictions, targets, weights, reduction, ignore_index):
"""Negative log likelihood loss on the input data.
output{n, i_1, i_2, ..., i_k} = -p * w
where t = target{n, i_1, i_2, ..., i_k}
p = predictions{n, t, i_1, i_2, i_k}
w = weights{n, i_1, i_2, ..., i_k} if t != ignore_index else 0
result = reduction(output)
Parameters
----------
predictions : tvm.te.Tensor
(k+2)-D with shape (N, C, d_1, d_2, ..., d_k),
where C is the number of target classes
targets : tvm.te.Tensor
(k+1)-D with shape (N, d_1, d_2, ..., d_k)
The target value of the input.
weights : tvm.te.Tensor
1-D with shape (C,)
The weight of each target value.
reduction : string
The reduction method to apply to output.
Can be "mean", "sum" or "none".
ignore_index : int
The target value to ignore.
Returns
-------
output : tvm.te.Tensor
a scalar if the reduction type is "mean" or "sum",
otherwise the same shape as `target`.
"""
return cpp.nn.nll_loss(predictions, targets, weights, reduction, ignore_index)
+238
View File
@@ -0,0 +1,238 @@
# 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=invalid-name
# ruff: noqa: E731
"""General LSTM implementation using TE scan."""
from tvm import te, tirx
from tvm.topi import tag
def lstm(
Xs,
Wi,
Wh,
Bi=None,
Bh=None,
h_init=None,
c_init=None,
proj=None,
p_i=None,
p_f=None,
p_o=None,
f_act=tirx.sigmoid,
g_act=tirx.tanh,
h_act=tirx.tanh,
reverse=False,
weight_layout: str = "IFGO",
):
"""General LSTM implemented using TE scan.
Parameters
----------
Xs : te.Tensor
Input sequence with shape `(seq_len, batch_size, in_dim)`
Wi : te.Tensor
Input weight matrix with shape `(4 * hidden_dim, in_dim)`. The weights are packed according
to `weight_layout`.
Wh : te.Tensor
Hidden weight matrix with shape `(4 * hidden_dim, hidden_dim or proj_dim)`. Packed as `Wh`.
Bi : te.Tensor, optional
Input bias with shape `(4 * hidden_dim,)`, by default None. Packed as `Wh`.
Bh : te.Tensor, optional
Hidden bias with shape as `Bi`, by default None. Packed as `Wh`.
h_init : te.Tensor, optional
Initial hidden state with shape `(batch_size, hidden_dim or proj_dim)`, zero if None
c_init : te.Tensor, optional
Initial cell state with same shape as `h_init`, zero if None
proj : te.Tensor, optional
Projection matrix with shape `(proj_dim, hidden_dim)`, by default None
p_i, p_f, p_o : te.Tensor, optional
Peephole LSTM matrices with shape `(batch_size, hidden_dim)`, by default None
f_act, g_act, h_act : F, optional
Gate activation functions
reverse : bool, optional
Whether to process `Xs` in reverse, by default False
weight_layout : str, optional
The packed weight layout for gates, by default "IFGO". Note: I = input, F = forget,
G = cell, O = output.
Returns
-------
result : te.Tensor, te.Tensor
Tuple of hidden states (with shape `(seq_len, batch_size, hidden_dim or proj_dim)`), and
cell states (with shape `(seq_len, batch_size, hidden_dim)`).
"""
assert len(weight_layout) == 4 and sorted(weight_layout) == sorted("IFGO"), (
f'given weight layout "{weight_layout}" is not a permutation of "IFGO"'
)
i_gate_idx = weight_layout.find("I")
f_gate_idx = weight_layout.find("F")
g_gate_idx = weight_layout.find("G")
o_gate_idx = weight_layout.find("O")
seq_len, batch_size, in_dim = Xs.shape
assert Wi.shape[0] % 4 == 0, (
f"dim 0 of input weight should be 4 * hidden_dim, but {Wi.shape[0]} is not divisible by 4"
)
hidden_dim = Wi.shape[0] // 4
proj_dim = hidden_dim
if proj is not None:
proj_dim = proj.shape[0]
# te.scan uses up 1 element for the initial value
scan_len = seq_len + 1
# precompute input-hidden matmul outside the scan
ki = te.reduce_axis((0, in_dim), name="ki2h")
Xi2h = te.compute(
(seq_len * batch_size, 4 * hidden_dim),
lambda tb, ij: te.sum(Xs[(tb // batch_size), tb % batch_size, ki] * Wi[ij, ki], axis=ki),
name="Xi2h",
)
if Bi is not None:
Xi2h = te.compute(
Xi2h.shape, lambda tb, ij: Xi2h[tb, ij] + Bi[ij], name="Xi2h_bias", tag=tag.INJECTIVE
)
h_state = te.placeholder((scan_len, batch_size, proj_dim), name="h_state")
c_state = te.placeholder((scan_len, batch_size, hidden_dim), name="c_state")
h_init = te.compute(
(1, batch_size, proj_dim),
lambda _, b, i: h_init[b, i] if h_init is not None else 0.0,
name="h_init",
)
c_init = te.compute(
(1, batch_size, hidden_dim),
lambda _, b, i: c_init[b, i] if c_init is not None else 0.0,
name="c_init",
)
# begin scan computations, first the (batched) hidden-hidden dense
kh = te.reduce_axis((0, proj_dim), name="kh2h")
s_h2h = te.compute(
(scan_len, batch_size, 4, hidden_dim),
lambda t, b, i, j: te.sum(h_state[t - 1, b, kh] * Wh[i * hidden_dim + j, kh], axis=kh),
name="s_h2h",
)
if Bh is not None:
s_h2h = te.compute(
s_h2h.shape,
lambda t, b, i, j: s_h2h[t, b, i, j] + Bh[i * hidden_dim + j],
name="s_h2h_bias",
tag=tag.INJECTIVE,
)
# helper to reverse time if scanning backwards
get_x_t = lambda t: seq_len - t if reverse else t - 1
gates = te.compute(
(scan_len, batch_size, 4, hidden_dim),
lambda t, b, i, j: (
Xi2h[get_x_t(t) * batch_size + b, i * hidden_dim + j] + s_h2h[t, b, i, j]
),
name="gates",
tag=tag.INJECTIVE,
)
# helper to correctly read each gate dense from the batched output
read_gate = lambda t, b, j, idx: gates[t, b, idx, j]
gate_shape = (scan_len, batch_size, hidden_dim)
# compute the activated gates (and do some extra stuff if peephole weights are present)
if p_i is not None and p_f is not None:
i_gate = te.compute(
gate_shape,
lambda t, b, j: f_act(
read_gate(t, b, j, i_gate_idx) + p_i[b, j] * c_state[t - 1, b, j]
),
name="i_gate_p",
tag=tag.INJECTIVE,
)
f_gate = te.compute(
gate_shape,
lambda t, b, j: f_act(
read_gate(t, b, j, f_gate_idx) + p_f[b, j] * c_state[t - 1, b, j]
),
name="f_gate_p",
tag=tag.INJECTIVE,
)
else:
i_gate = te.compute(
gate_shape,
lambda *i: f_act(read_gate(*i, i_gate_idx)),
name="i_gate",
tag=tag.INJECTIVE,
)
f_gate = te.compute(
gate_shape,
lambda *i: f_act(read_gate(*i, f_gate_idx)),
name="f_gate",
tag=tag.INJECTIVE,
)
g_gate = te.compute(
gate_shape, lambda *i: g_act(read_gate(*i, g_gate_idx)), name="g_gate", tag=tag.INJECTIVE
)
next_c = te.compute(
gate_shape,
lambda t, b, j: f_gate[t, b, j] * c_state[t - 1, b, j] + i_gate[t, b, j] * g_gate[t, b, j],
name="next_c",
)
if p_o is not None:
o_gate = te.compute(
gate_shape,
lambda t, b, j: f_act(read_gate(t, b, j, o_gate_idx) + p_o[b, j] * next_c[t, b, j]),
name="o_gate_p",
tag=tag.INJECTIVE,
)
else:
o_gate = te.compute(
gate_shape,
lambda *i: f_act(read_gate(*i, o_gate_idx)),
name="o_gate",
tag=tag.INJECTIVE,
)
next_h = te.compute(gate_shape, lambda *i: o_gate(*i) * h_act(next_c(*i)), name="next_h")
# project hidden state back to proj_dim if projection matrix is present
if proj is not None:
kr = te.reduce_axis((0, hidden_dim), name="kh2p")
next_h = te.compute(
(scan_len, batch_size, proj_dim),
lambda t, b, j: te.sum(next_h[t, b, kr] * proj[j, kr], axis=kr),
name="next_h_proj",
)
scan_h, scan_c = te.scan(
[h_init, c_init], [next_h, next_c], [h_state, c_state], name="lstm_scan"
)
# drop the initial values, TODO(@altanh): is there a better way?
scan_h = te.compute(
(seq_len, batch_size, proj_dim), lambda t, b, j: scan_h[t + 1, b, j], name="hidden_states"
)
scan_c = te.compute(
(seq_len, batch_size, hidden_dim), lambda t, b, j: scan_c[t + 1, b, j], name="cell_states"
)
return scan_h, scan_c
+100
View File
@@ -0,0 +1,100 @@
# 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=invalid-name, line-too-long
"""Operators of one-to-one-mapping on the first input"""
import tvm
from tvm import te
from .. import tag
@tvm.te.tag_scope(tag=tag.BROADCAST)
def scale_shift_nchw(Input, Scale, Shift):
"""Batch normalization operator in inference.
Parameters
----------
Input : tvm.te.Tensor
4-D input tensor, NCHW layout [batch, channel, height, width]
Scale : tvm.te.Tensor
Scale tensor, 1-D of size channel number
Shift : tvm.te.Tensor
Shift tensor, 1-D of size channel number
Returns
-------
Output : tvm.te.Tensor
Output tensor, layout is NCHW
"""
return te.compute(
Input.shape, lambda b, c, i, j: Input[b, c, i, j] * Scale[c] + Shift[c], name="ScaleShift"
)
@tvm.te.tag_scope(tag=tag.BROADCAST)
def scale_shift_nhwc(Input, Scale, Shift):
"""Batch normalization operator in inference.
Parameters
----------
Input : tvm.te.Tensor
4-D input tensor, NHWC layout [batch, height, width, channel]
Scale : tvm.te.Tensor
Scale tensor, 1-D of size channel number
Shift : tvm.te.Tensor
Shift tensor, 1-D of size channel number
Returns
-------
Output : tvm.te.Tensor
Output tensor, layout is NHWC
"""
return te.compute(
Input.shape, lambda b, i, j, c: Input[b, i, j, c] * Scale[c] + Shift[c], name="ScaleShift"
)
@tvm.te.tag_scope(tag=tag.BROADCAST)
def scale_shift_nchwc(Input, Scale, Shift):
"""Batch normalization operator in inference.
Parameters
----------
Input : tvm.te.Tensor
5-D input tensor, NCHWc layout [batch, channel_chunk, height, width, channel_block]
Scale : tvm.te.Tensor
Scale tensor, 2-D of size [channel_chunk, channel_block]
Shift : tvm.te.Tensor
Shift tensor, 2-D of size [channel_chunk, channel_block]
Returns
-------
Output : tvm.te.Tensor
Output tensor, layout is NHWC
"""
return te.compute(
Input.shape,
lambda b, cc, i, j, cb: Input[b, cc, i, j, cb] * Scale[cc, cb] + Shift[cc, cb],
name="ScaleShift",
)
+316
View File
@@ -0,0 +1,316 @@
# 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.
"""Pad the data by constant value"""
import tvm
from tvm import te
from tvm.tirx import if_then_else
from .. import tag
from ..utils import equal_const_int
def get_padded_shape(data, pad_before, pad_after=None):
"""
Calculates the output shape of a tensor after applying padding.
Args:
data (tvm.te.Tensor): The input tensor to which padding is applied.
pad_before : list / tuple of n ints
Pad width on each dimension to pad the before the axis begin.
pad_after : list / tuple of n ints, optional
Pad width each dimension to pad the after the axis end.
Raises:
ValueError: If `pad_before` or `pad_after` lengths mismatch with `data` dimensions.
Returns:
tuple: A tuple representing the padded shape of the tensor.
"""
n = data.ndim
pad_after = pad_after if pad_after else pad_before
if len(pad_before) != n:
raise ValueError(f"pad_before length {len(pad_before)} != input dims {n}")
if len(pad_after) != n:
raise ValueError(f"pad_after length {len(pad_after)} != input dims {n}")
ana = tvm.arith.Analyzer()
out_shape = tuple(ana.simplify(data.shape[i] + pad_before[i] + pad_after[i]) for i in range(n))
return out_shape
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",pad")
def pad(data, pad_before, pad_after=None, pad_value=0.0, name="PadInput", attrs=None):
"""Pad Input with using pad values.
Parameters
----------
data : tvm.te.Tensor
n-D input, can be any layout.
pad_before : list / tuple of n ints
Pad width on each dimension to pad the before the axis begin.
pad_after : list / tuple of n ints, optional
Pad width each dimension to pad the after the axis end.
pad_value : float, optional
The value to be padded.
name : str, optional
The name prefix operators generated
Returns
-------
Output : tvm.te.Tensor
n-D, the same layout as Input.
"""
n = len(data.shape)
pad_after = pad_after if pad_after else pad_before
if len(pad_before) != n:
raise ValueError(f"Input dimension and pad_before dismatch : {n} vs {len(pad_before)}")
if len(pad_after) != n:
raise ValueError(f"Input dimension and pad_after dismatch : {n} vs {len(pad_after)}")
ana = tvm.arith.Analyzer()
dshape = []
for dim in data.shape:
dshape.append(dim)
out_shape = tuple(ana.simplify(dshape[i] + pad_before[i] + pad_after[i]) for i in range(n))
pad_value = (
pad_value if tvm.ir.is_prim_expr(pad_value) else tvm.tirx.const(pad_value, data.dtype)
)
def _pad(*indices):
not_zero = []
index_tuple = []
for i in range(n):
if equal_const_int(pad_before[i], 0) and equal_const_int(pad_after[i], 0):
index_tuple.append(indices[i])
else:
index_tuple.append(indices[i] - pad_before[i])
not_zero.append(indices[i] >= pad_before[i])
not_zero.append(indices[i] < data.shape[i] + pad_before[i])
if not_zero:
not_zero = tvm.tirx.all(*not_zero)
return tvm.tirx.if_then_else(not_zero, data(*index_tuple), pad_value)
return data(*index_tuple)
return te.compute(out_shape, _pad, name=name, attrs=attrs)
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",pad")
def mirror_pad(data, pad_before, pad_after=None, mode="SYMMETRIC", name="MirrorPadInput"):
"""Pad Input with mirroring either symmetric or reflected.
Parameters
----------
data : tvm.te.Tensor
n-D input, can be any layout.
pad_before : list / tuple of n ints
Pad width on each dimension to pad the before the axis begin.
pad_after : list / tuple of n ints, optional
Pad width each dimension to pad the after the axis end.
mode: str, optional
Type of mirror padding to apply. Must be SYMMETRIC or REFLECT
name : str, optional
The name prefix operators generated
Returns
-------
Output : tvm.te.Tensor
n-D, the same layout as Input.
"""
n = len(data.shape)
pad_after = pad_after if pad_after else pad_before
if len(pad_before) != n:
raise ValueError(f"Input dimension and pad_before dismatch : {n} vs {len(pad_before)}")
if len(pad_after) != n:
raise ValueError(f"Input dimension and pad_after dismatch : {n} vs {len(pad_after)}")
ana = tvm.arith.Analyzer()
out_shape = tuple(ana.simplify(data.shape[i] + pad_before[i] + pad_after[i]) for i in range(n))
assert mode in ("SYMMETRIC", "REFLECT")
mode = int(mode == "SYMMETRIC")
def _pad(*indices):
index_tuple = []
above = []
below = []
for i in range(n):
if equal_const_int(pad_before[i], 0) and equal_const_int(pad_after[i], 0):
index_tuple.append(indices[i])
above.append(False)
below.append(False)
else:
index_tuple.append(indices[i] - pad_before[i])
above.append(indices[i] >= data.shape[i] + pad_before[i])
below.append(indices[i] < pad_before[i])
mapped_tuple = []
for i, axis in enumerate(index_tuple):
mapped_axis = tvm.tirx.if_then_else(below[i], -axis - mode, axis)
mapped_axis = tvm.tirx.if_then_else(
above[i], (2 * (data.shape[i] - 1)) - axis + mode, mapped_axis
)
mapped_tuple.append(mapped_axis)
return data(*mapped_tuple)
return te.compute(out_shape, _pad, name=name)
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",pad")
def reflect_pad(data, pad_before, pad_after=None, name="ReflectPadInput"):
"""
Apply reflect padding to the input tensor.
Parameters
----------
data : tvm.te.Tensor
Input tensor.
pad_before : List[int]
Amount to pad before each dimension.
pad_after : List[int], optional
Amount to pad after each dimension. If None, defaults to pad_before.
name : str
Name of the resulting tensor.
Returns
-------
out : tvm.te.Tensor
Reflect-padded tensor.
"""
out_shape = get_padded_shape(data, pad_before, pad_after)
def _pad(*indices):
index_tuple = []
for i in range(data.ndim):
idx = indices[i]
size = data.shape[i]
before = pad_before[i]
orig_idx = idx - before
reflected_idx = if_then_else(
orig_idx < 0,
-orig_idx, # reflect from start (no repeat)
if_then_else(
orig_idx >= size,
(2 * size - 2) - orig_idx, # reflect from end
orig_idx,
),
)
index_tuple.append(reflected_idx)
return data(*index_tuple)
return te.compute(out_shape, _pad, name=name)
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",pad")
def replicate_pad(data, pad_before, pad_after=None, name="ReplicatePadInput"):
"""
Apply replicate padding (edge padding) to the input tensor.
Parameters
----------
data : tvm.te.Tensor
Input tensor.
pad_before : List[int]
Amount to pad before each dimension.
pad_after : List[int], optional
Amount to pad after each dimension. If None, defaults to pad_before.
name : str
Name of the resulting tensor.
Returns
-------
out : tvm.te.Tensor
Replicate-padded tensor.
"""
out_shape = get_padded_shape(data, pad_before, pad_after)
def _pad(*indices):
index_tuple = []
for i in range(data.ndim):
idx = indices[i]
size = data.shape[i]
before = pad_before[i]
orig_idx = idx - before
clamped_idx = if_then_else(
orig_idx < 0,
tvm.tirx.const(0, "int32"), # replicate first element
if_then_else(
orig_idx >= size,
size - 1, # replicate last element
orig_idx,
),
)
index_tuple.append(clamped_idx)
return data(*index_tuple)
return te.compute(out_shape, _pad, name=name)
@tvm.te.tag_scope(tag=tag.INJECTIVE + ",pad")
def circular_pad(data, pad_before, pad_after=None, name="CircularPadInput"):
"""
Apply circular padding (wrap around) to the input tensor.
Parameters
----------
data : tvm.te.Tensor
Input tensor.
pad_before : List[int]
Amount to pad before each dimension.
pad_after : List[int], optional
Amount to pad after each dimension. If None, defaults to pad_before.
name : str
Name of the resulting tensor.
Returns
-------
out : tvm.te.Tensor
Circular-padded tensor.
"""
out_shape = get_padded_shape(data, pad_before, pad_after)
def _pad(*indices):
index_tuple = []
for i in range(data.ndim):
idx = indices[i]
size = data.shape[i]
before = pad_before[i]
orig_idx = idx - before
wrapped_idx = tvm.tirx.indexmod(orig_idx + size, size)
index_tuple.append(wrapped_idx)
return data(*index_tuple)
return te.compute(out_shape, _pad, name=name)
+75
View File
@@ -0,0 +1,75 @@
# 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.
# ruff: noqa: RUF005
"""TVM operator pixel shuffle compute."""
import tvm
def pixel_shuffle(data, upscale_factor, name="PixelShuffle"):
"""PixelShuffle operator that rearranges elements in a tensor of shape
[..., C * r * r, H, W] to [..., C, H * r, W * r].
Parameters
----------
data : tvm.te.Tensor
N-D input tensor with at least 3 dimensions. Channel must be at index -3.
upscale_factor : int
The upscale factor (r).
name : str
Name of the output tensor.
Returns
-------
output : tvm.te.Tensor
Pixel shuffled tensor with shape [..., C, H*r, W*r]
"""
assert isinstance(upscale_factor, int) and upscale_factor > 0
ndim = len(data.shape)
assert ndim >= 3, "Input must be at least 3D"
upscale_factor_const = tvm.tirx.const(upscale_factor, "int32")
c_in, h_in, w_in = data.shape[-3], data.shape[-2], data.shape[-1]
c_out = tvm.tirx.floordiv(c_in, upscale_factor_const * upscale_factor_const)
h_out = h_in * upscale_factor_const
w_out = w_in * upscale_factor_const
out_shape = list(data.shape[:-3]) + [c_out, h_out, w_out]
def _compute(*indices):
batch_indices = indices[:-3]
c_out_idx, h_out_idx, w_out_idx = indices[-3], indices[-2], indices[-1]
h_idx = tvm.tirx.floordiv(h_out_idx, upscale_factor_const)
h_offset = h_out_idx % upscale_factor_const
w_idx = tvm.tirx.floordiv(w_out_idx, upscale_factor_const)
w_offset = w_out_idx % upscale_factor_const
c_in_idx = (
(c_out_idx * upscale_factor_const * upscale_factor_const)
+ (h_offset * upscale_factor_const)
+ w_offset
)
index_tuple = batch_indices + (c_in_idx, h_idx, w_idx)
return data[index_tuple]
return tvm.te.compute(out_shape, _compute, name=name)
+406
View File
@@ -0,0 +1,406 @@
# 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.
"""TVM operator pooling compute."""
from .. import cpp
POOL_TYPE_CODE = {"avg": 0, "max": 1}
def global_pool(data, pool_type, layout="NCHW"):
"""Perform global pooling on height and width dimension of data.
It decides the height and width dimension according to the layout string,
in which 'W' and 'H' means width and height respectively.
Width and height dimension cannot be split.
For example, NCHW, NCHW16c, etc. are valid for pool,
while NCHW16w, NCHW16h are not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
data : tvm.te.Tensor
n-D with shape of layout
pool_type : str
Pool type, 'max' or 'avg'
layout : str
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCHW16c can describe a 5-D tensor of
[batch_size, channel, height, width, channel_block],
in which channel_block=16 is a split of dimension channel.
Returns
-------
output : tvm.te.Tensor
n-D in same layout with height and width dimension size of 1.
e.g., for NCHW, the output shape will be [batch, channel, 1, 1]
"""
return cpp.nn.global_pool(data, POOL_TYPE_CODE[pool_type], layout)
def pool_grad(
grads,
data,
kernel,
stride,
padding,
pool_type,
ceil_mode=False,
count_include_pad=True,
layout="NCHW",
):
"""Gradient of pooling on height and width dimension of data.
It decides the height and width dimension according to the layout string,
in which 'W' and 'H' means width and height respectively.
Width and height dimension cannot be split.
For example, NCHW, NCHW16c, etc. are valid for pool,
while NCHW16w, NCHW16h are not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
grads : tvm.te.Tensor
n-D with shape of layout
data : tvm.te.Tensor
n-D with shape of layout
kernel : list/tuple of two ints
Kernel size, [kernel_height, kernel_width]
stride : list/tuple of two ints
Stride size, [stride_height, stride_width]
padding : list/tuple of four ints
Pad size, [pad_top, pad_left, pad_bottom, pad_right]]
pool_type : str
Pool type, 'max' or 'avg'
ceil_mode : bool
Whether to use ceil when calculating output size.
count_include_pad: bool
Whether include padding in the calculation when pool_type is 'avg'
layout: string
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCHW16c can describe a 5-D tensor of
[batch_size, channel, height, width, channel_block],
in which channel_block=16 is a split of dimension channel.
Returns
-------
output : tvm.te.Tensor
n-D in the same layout
"""
return cpp.nn.pool_grad(
grads,
data,
kernel,
stride,
padding,
POOL_TYPE_CODE[pool_type],
ceil_mode,
layout,
count_include_pad,
)
def adaptive_pool(data, output_size, pool_type, layout="NCHW"):
"""Perform pooling on height and width dimension of data.
The pooling kernel and stride sizes are automatically chosen for desired
output sizes.
It decides the height and width dimension according to the layout string,
in which 'W' and 'H' means width and height respectively.
Width and height dimension cannot be split.
For example, NCHW, NCHW16c, etc. are valid for pool,
while NCHW16w, NCHW16h are not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
data : tvm.te.Tensor
n-D with shape of layout
output_size : tuple of int
output height and width.
pool_type : str
Pool type, 'max' or 'avg'
layout: string
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCHW16c can describe a 5-D tensor of
[batch_size, channel, height, width, channel_block],
in which channel_block=16 is a split of dimension channel.
Returns
-------
output : tvm.te.Tensor
n-D in the same layout
"""
return cpp.nn.adaptive_pool(data, output_size, POOL_TYPE_CODE[pool_type], layout)
def adaptive_pool1d(data, output_size, pool_type, layout="NCW"):
"""Perform pooling on three dimensional data.
See the two dimensional version above for details.
"""
return cpp.nn.adaptive_pool1d(data, output_size, POOL_TYPE_CODE[pool_type], layout)
def adaptive_pool3d(data, output_size, pool_type, layout="NCDHW"):
"""Perform pooling on three dimensional data.
See the two dimensional version above for details.
"""
return cpp.nn.adaptive_pool3d(data, output_size, POOL_TYPE_CODE[pool_type], layout)
def pool1d(
data,
kernel,
stride,
dilation,
padding,
pool_type,
ceil_mode=False,
layout="NCW",
count_include_pad=True,
):
"""Perform pooling on width dimension of data.
Width axis is determined according to the layout string.
in which 'w' means width.
Width dimension cannot be split.
For example, NCW, NCW16c, etc. are valid for pool,
while NCW16w is not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
data : tvm.te.Tensor
n-D with shape of layout
kernel : list/tuple of one int or int
Kernel size, [kernel_width]
stride : list/tuple of one int or int
Stride size, [stride_width]
dilation: list/tuple of two ints
Dilation size, [dilation_height, dilation_width]
padding : list/tuple of two ints
Pad size, [pad_left, pad_right]
pool_type : str
Pool type, 'max' or 'avg'
ceil_mode : bool
Whether to use ceil when calculating output size.
layout: string
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCW16c can describe a 4-D tensor of
[batch_size, channel, width, channel_block],
in which channel_block=16 is a split of dimension channel.
count_include_pad: bool
Whether include padding in the calculation when pool_type is 'avg'
Returns
-------
output : tvm.te.Tensor
n-D in the same layout
"""
if isinstance(kernel, int):
kernel = [
kernel,
]
if isinstance(stride, int):
stride = [
stride,
]
return cpp.nn.pool1d(
data,
kernel,
stride,
dilation,
padding,
POOL_TYPE_CODE[pool_type],
ceil_mode,
layout,
count_include_pad,
)
def pool2d(
data,
kernel,
stride,
dilation,
padding,
pool_type,
ceil_mode=False,
layout="NCHW",
count_include_pad=True,
):
"""Perform pooling on height and width dimension of data.
It decides the height and width dimension according to the layout string,
in which 'W' and 'H' means width and height respectively.
Width and height dimension cannot be split.
For example, NCHW, NCHW16c, etc. are valid for pool,
while NCHW16w, NCHW16h are not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
data : tvm.te.Tensor
n-D with shape of layout
kernel : list/tuple of two ints
Kernel size, [kernel_height, kernel_width]
stride : list/tuple of two ints
Stride size, [stride_height, stride_width]
dilation: list/tuple of two ints
Dilation size, [dilation_height, dilation_width]
padding : list/tuple of four ints
Pad size, [pad_top, pad_left, pad_bottom, pad_right]]
pool_type : str
Pool type, 'max' or 'avg'
ceil_mode : bool
Whether to use ceil when calculating output size.
layout: string
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCHW16c can describe a 5-D tensor of
[batch_size, channel, height, width, channel_block],
in which channel_block=16 is a split of dimension channel.
count_include_pad: bool
Whether include padding in the calculation when pool_type is 'avg'
Returns
-------
output : tvm.te.Tensor
n-D in the same layout
"""
return cpp.nn.pool2d(
data,
kernel,
stride,
dilation,
padding,
POOL_TYPE_CODE[pool_type],
ceil_mode,
layout,
count_include_pad,
)
def pool3d(
data,
kernel,
stride,
dilation,
padding,
pool_type,
ceil_mode=False,
layout="NCDHW",
count_include_pad=True,
):
"""Perform pooling on depth, height and width dimension of data.
It decides the depth, height and width dimension according to the layout string,
in which 'D', 'W' and 'H' means depth, width and height respectively.
Depth, width and height dimension cannot be split.
For example, NCDHW, NCDHW16c, etc. are valid for pool,
while NCDHW16d, NCDHW16w, NCDHW16h are not.
See parameter `layout` for more information of the layout string convention.
Parameters
----------
data : tvm.te.Tensor
n-D with shape of layout
kernel : list/tuple of three ints
Kernel size, [kernel_depth, kernel_height, kernel_width]
stride : list/tuple of three ints
Stride size, [stride_depth, stride_height, stride_width]
dilation: list/tuple of two ints
Dilation size, [dilation_height, dilation_width]
padding : list/tuple of six ints
Pad size, [pad_front, pad_top, pad_left, pad_back, pad_bottom, pad_right]
pool_type : str
Pool type, 'max' or 'avg'
ceil_mode : bool
Whether to use ceil when calculating output size.
layout: string
Layout of the input data.
The layout is supposed to be composed of upper cases, lower cases and numbers,
where upper case indicates a dimension and
the corresponding lower case with factor size indicates the split dimension.
For example, NCDHW16c can describe a 6-D tensor of
[batch_size, channel, depth, height, width, channel_block],
in which channel_block=16 is a split of dimension channel.
count_include_pad: bool
Whether include padding in the calculation when pool_type is 'avg'
Returns
-------
output : tvm.te.Tensor
n-D in the same layout
"""
return cpp.nn.pool3d(
data,
kernel,
stride,
dilation,
padding,
POOL_TYPE_CODE[pool_type],
ceil_mode,
layout,
count_include_pad,
)
+193
View File
@@ -0,0 +1,193 @@
# 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.
"""Quantized Neural Network (QNN) Operators"""
import tvm
from tvm import te, tirx, topi
SQNN_DISABLE = 0
SQNN_INT8 = 1
SQNN_UINT8 = 2
SQNN_INT32 = 3
SQNN_DTYPE_TO_CODE = {
"disable": SQNN_DISABLE,
"int8": SQNN_INT8,
"uint8": SQNN_UINT8,
"int32": SQNN_INT32,
}
SQNN_CODE_TO_DTYPE = {v: k for k, v in SQNN_DTYPE_TO_CODE.items()}
@tvm.te.tag_scope(tag=topi.tag.ELEMWISE)
def simulated_quantize(data, out_dtype, output_scale=None, output_zero_point=None, axis=-1):
"""Simulated QNN quantize operator that mimics QNN outputs without changing datatype.
The benefit of this operator over true QNN quantize is that this operator allows dynamic
datatype selection and can operate on both per-channel and scalar scales and zero points while
QNN quantize requires both of these to be fixed at compile time.
Parameters
----------
data: tvm.te.Tensor
An N-D input tensor to the operator.
out_dtype: tvm.te.Tensor
A scalar variable that indicates which datatype to simulate quantization with. Use
SQNN_DTYPE_TO_CODE to convert a dtype string into the corresponding variable
value.
output_scale: tvm.te.Tensor, optional
A scalar tensor representing the scale to use when quantizing to integer datatypes.
When it contains more than a single value, N must match the number of channels in data.
output_zero_point: tvm.te.Tensor, optional
A 1-D tensor representing the zero point to use when quantizing to integer datatypes.
When it contains more than a single value, N must match the number of channels in data.
axis: int, optional
The channel axis for quantization. Default value is -1 which corresponds to the last axis.
"""
# When disabled, just pass through the input values.
def _compute_pass_through(value, *indices):
return value[indices]
# Simulate quantization for arbitrary integer datatypes. The computation for all datatypes is:
# Q_output = clip((round(input_tensor/output_scale) + output_zero_point),
# out_dtype::min,
# out_dtype::max)
def _compute_intn(dtype, value, *indices):
assert output_scale is not None and output_zero_point is not None
const_min = tvm.tirx.min_value(dtype)
const_max = tvm.tirx.max_value(dtype)
# Use indexmod to handle both scalar and per-channel QNN parameters.
scale_idx = tirx.indexmod(indices[axis], topi.shape(output_scale)[0])
zp_idx = tirx.indexmod(indices[axis], topi.shape(output_zero_point)[0])
return te.max(
te.min(
te.round(value[indices] / output_scale[scale_idx]) + output_zero_point[zp_idx],
const_max,
),
const_min,
)
# Use an if chain to dynamically return the proper quantization based on the input datatype.
# This allows the op to compile once but apply different quantization approaches
# using a variable datatype input.
def _dispatch_sim_quantize(value):
pass_through_value = te.compute(
data.shape, lambda *indices: _compute_pass_through(value, *indices)
)
int8_value = te.compute(
data.shape,
lambda *indices: tirx.if_then_else(
out_dtype.equal(SQNN_DTYPE_TO_CODE["int8"]),
_compute_intn("int8", value, *indices),
pass_through_value[indices],
),
)
uint8_value = te.compute(
data.shape,
lambda *indices: tirx.if_then_else(
out_dtype.equal(SQNN_DTYPE_TO_CODE["uint8"]),
_compute_intn("uint8", value, *indices),
int8_value[indices],
),
)
int32_value = te.compute(
data.shape,
lambda *indices: tirx.if_then_else(
out_dtype.equal(SQNN_DTYPE_TO_CODE["int32"]),
_compute_intn("int32", value, *indices),
uint8_value[indices],
),
)
return int32_value
return te.compute(data.shape, lambda *indices: _dispatch_sim_quantize(data)[indices])
@tvm.te.tag_scope(tag=topi.tag.ELEMWISE)
def simulated_dequantize(data, in_dtype, input_scale=None, input_zero_point=None, axis=-1):
"""Simulated QNN dequantize operator that mimics QNN outputs without changing datatype.
The benefit of this operator over true QNN dequantize is that this operator allows dynamic
datatype selection and can operate on both per-channel and scalar scales and zero points while
QNN dequantize requires both of these to be fixed at compile time.
Parameters
----------
data: tvm.te.Tensor
An N-D input tensor to the operator.
in_dtype: tvm.te.Tensor
A scalar variable that indicates which datatype to simulate dequantization with. Use
SQNN_DTYPE_TO_CODE to convert a dtype string into the corresponding variable
value.
input_scale: tvm.te.Tensor, optional
A scalar tensor representing the scale to use when dequantizing from integer datatypes.
When it contains more than a single value, N must match the number of channels in data.
input_zero_point: tvm.te.Tensor, optional
A 1-D tensor representing the zero point to use when dequantizing from integer datatypes.
When it contains more than a single value, N must match the number of channels in data.
axis: int, optional
The channel axis for quantization. Default value is -1 which corresponds to the last axis.
"""
# When disabled simply return the input tensor.
def _compute_pass_through(value, *indices):
return value[indices]
# Simulate dequantization for arbitrary integer datatypes. The computation for all datatypes is:
# DQ_output = (input - zero_point) * scale
def _compute_intn(value, *indices):
assert input_scale is not None and input_zero_point is not None
# Use indexmod to handle both scalar and per-channel QNN parameters.
scale_idx = tirx.indexmod(indices[axis], topi.shape(input_scale)[0])
zp_idx = tirx.indexmod(indices[axis], topi.shape(input_zero_point)[0])
return (value[indices] - input_zero_point[zp_idx]) * input_scale[scale_idx]
# Use an if chain to dynamically return the proper dequantization based on the input datatype.
# This allows the op to compile once but apply different quantization approaches
# using a variable datatype input.
def _dispatch_sim_dequantize(value):
pass_through_value = te.compute(
data.shape, lambda *indices: _compute_pass_through(value, *indices)
)
intn_condition = tvm.te.any(
in_dtype.equal(SQNN_DTYPE_TO_CODE["int8"]),
in_dtype.equal(SQNN_DTYPE_TO_CODE["uint8"]),
in_dtype.equal(SQNN_DTYPE_TO_CODE["int32"]),
)
intn_value = te.compute(
data.shape,
lambda *indices: tirx.if_then_else(
intn_condition,
_compute_intn(value, *indices),
pass_through_value[indices],
),
)
return intn_value
return te.compute(data.shape, lambda *indices: _dispatch_sim_dequantize(data)[indices])
+44
View File
@@ -0,0 +1,44 @@
# 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.
"""Root mean square normalization operator."""
from .. import cpp
def rms_norm(data, weight, axis, epsilon=1e-5):
"""Root mean square normalization operator. The output will have the same data type as input.
Parameters
----------
data : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
weight: tvm.te.Tensor
K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and d_{axis_k} == r_k
axis : list of int
Axis over the normalization applied
epsilon : float
The epsilon value to avoid division by zero.
Returns
-------
result : tvm.te.Tensor
N-D with shape (d_0, d_1, ..., d_{N-1})
"""
return cpp.nn.rms_norm(data, weight, axis, epsilon)
+177
View File
@@ -0,0 +1,177 @@
# 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=invalid-name, pointless-exception-statement
# ruff: noqa: RUF005
"""TVM operator for softmax and log_softmax compute."""
import tvm
from tvm import te, topi
@tvm.te.tag_scope(tag="softmax_output")
def softmax(x, axis=-1):
"""Perform softmax activation on the data.
Parameters
----------
x : tvm.te.Tensor
can be any dimension
axis : int
channel axis
Returns
-------
output : tvm.te.Tensor
output shape is the same as input
"""
return softmax_common(x, axis, False)
@tvm.te.tag_scope(tag="fast_softmax_output")
def fast_softmax(x, axis=-1):
"""Perform softmax activation on the data.
Use approximation to compute exponent for faster speed.
Parameters
----------
x : tvm.te.Tensor
can be any dimension
axis : int
channel axis
Returns
-------
output : tvm.te.Tensor
output shape is the same as input
"""
return softmax_common(x, axis, True)
def softmax_common(x, axis, use_fast_exp):
"""The common part of softmax and fast_softmax"""
shape = x.shape
if axis < 0:
axis = len(shape) + axis
if axis >= len(shape):
ValueError("axis parameter should be less than input dim")
k1 = te.reduce_axis((0, shape[axis]), name="k")
k2 = te.reduce_axis((0, shape[axis]), name="k")
def insert_reduce_index(indices, reduce_index):
return indices[:axis] + (reduce_index,) + indices[axis:]
def get_non_reduce_indices(indices):
return tuple([var for (i, var) in enumerate(indices) if i != axis])
def _compute_max(*indices):
eval_range = insert_reduce_index(indices, k1)
return tvm.te.max(x[eval_range], axis=k1)
def _compute_delta(max_elem, *indices):
non_reduce_indices = get_non_reduce_indices(indices)
return x[indices] - max_elem[non_reduce_indices]
def _compute_exp(max_elem, *indices):
non_reduce_indices = get_non_reduce_indices(indices)
return te.exp(x[indices] - max_elem[non_reduce_indices])
def _compute_expsum(exp, *indices):
eval_range = insert_reduce_index(indices, k2)
return te.sum(exp[eval_range], axis=k2)
def _normalize(exp, expsum, *indices):
non_reduce_indices = get_non_reduce_indices(indices)
return exp[indices] / expsum[non_reduce_indices]
reduced_shape = tuple([dim for (i, dim) in enumerate(shape) if i != axis])
max_elem = te.compute(reduced_shape, _compute_max, name="T_softmax_maxelem")
if use_fast_exp:
delta = te.compute(
shape, lambda *indices: _compute_delta(max_elem, *indices), name="T_softmax_delta"
)
exp = topi.math.fast_exp(delta)
else:
exp = te.compute(
shape, lambda *indices: _compute_exp(max_elem, *indices), name="T_softmax_exp"
)
expsum = te.compute(
reduced_shape, lambda *indices: _compute_expsum(exp, *indices), name="T_softmax_expsum"
)
return te.compute(
shape,
lambda *indices: _normalize(exp, expsum, *indices),
name="T_softmax_norm",
attrs={"axis": axis},
)
@tvm.te.tag_scope(tag="log_softmax_output")
def log_softmax(x, axis=-1):
"""Perform log softmax activation on the data
Parameters
----------
x : tvm.te.Tensor
N-D input data
axis : int
channel axis
Returns
-------
output : tvm.te.Tensor
N-D output with same shape
"""
shape = x.shape
if axis < 0:
axis = len(shape) + axis
if axis >= len(shape):
ValueError("axis parameter should be less than input dim")
k1 = te.reduce_axis((0, shape[axis]), name="k")
k2 = te.reduce_axis((0, shape[axis]), name="k")
def insert_reduce_index(indices, reduce_index):
return indices[:axis] + (reduce_index,) + indices[axis:]
def get_non_reduce_indices(indices):
return tuple([var for (i, var) in enumerate(indices) if i != axis])
def _compute_max(*indices):
eval_range = insert_reduce_index(indices, k1)
return tvm.te.max(x[eval_range], axis=k1)
def _compute_expsum(max_elem, *indices):
eval_range = insert_reduce_index(indices, k2)
return te.sum(te.exp(x[eval_range] - max_elem[indices]), axis=k2)
def _normalize(max_elem, expsum, *indices):
non_reduce_indices = get_non_reduce_indices(indices)
return x[indices] - max_elem[non_reduce_indices] - te.log(expsum[non_reduce_indices])
reduced_shape = tuple([dim for (i, dim) in enumerate(shape) if i != axis])
max_elem = te.compute(reduced_shape, _compute_max, name="T_softmax_maxelem")
expsum = te.compute(reduced_shape, lambda *indices: _compute_expsum(max_elem, *indices))
return te.compute(
shape,
lambda *indices: _normalize(max_elem, expsum, *indices),
attrs={"axis": axis},
)
+52
View File
@@ -0,0 +1,52 @@
# 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=invalid-name
"""TVM operator space_to_batch_nd compute."""
from . import cpp
def space_to_batch_nd(data, block_shape, pad_before, pad_after, pad_value=0.0):
"""Perform batch to space transformation on the data
Parameters
----------
data : tvm.te.Tensor
N-D Tensor with shape [batch, spatial_shape, remaining_shapes],
where spatial_shape has M dimensions.
block_shape : list of ints
list of size [M] where M is number of spatial dims, specifies block
size for each spatial dimension.
pad_before : list of ints
list of shape [M] where M is number of spatial dims, specifies
zero-padding size before each spatial dimension.
pad_after : list of ints
list of shape [M] where M is number of spatial dims, specifies
zero-padding size after each spatial dimension.
pad_value : float, optional
The value used for padding.
Returns
-------
output : tvm.te.Tensor
"""
return cpp.nn.space_to_batch_nd(data, block_shape, pad_before, pad_after, pad_value)
+88
View File
@@ -0,0 +1,88 @@
# 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=invalid-name
"""TVM operator space_to_depth compute."""
import tvm
from tvm import te
from .. import tag
def space_to_depth(data, block_size, layout="NCHW"):
"""Perform space to depth transformation on the data
Parameters
----------
data : tvm.te.Tensor
4-D tensor in either NCHW or NHWC layout.
block_size : int
Size of blocks to decompose into channel dimension.
layout : string
Either NCHW or NHWC, indicating data layout.
Returns
-------
output : tvm.te.Tensor
Output of shape [N, C * block_size**2, H / block_size, W / block_size]
"""
if layout == "NCHW":
in_n, in_c, in_h, in_w = data.shape
output_shape = [
in_n,
in_c * block_size * block_size,
tvm.tirx.truncdiv(in_h, block_size),
tvm.tirx.truncdiv(in_w, block_size),
]
elif layout == "NHWC":
in_n, in_h, in_w, in_c = data.shape
output_shape = [
in_n,
tvm.tirx.truncdiv(in_h, block_size),
tvm.tirx.truncdiv(in_w, block_size),
in_c * block_size * block_size,
]
else:
raise ValueError("Only NCHW and NHWC layouts are currently supported.")
def _get_indices(*indices):
if layout == "NCHW":
n, c, y, x = indices
elif layout == "NHWC":
n, y, x, c = indices
return n, c, y, x
def _get_pixel(n, c, y, x):
block_offset = tvm.tirx.truncdiv(c, in_c)
channel_idx = tvm.tirx.truncmod(c, in_c)
x_idx = tvm.tirx.truncmod(block_offset, block_size)
y_idx = tvm.tirx.truncdiv(block_offset, block_size)
if layout == "NCHW":
output = data(n, channel_idx, y_idx + (y * block_size), x_idx + (x * block_size))
else:
output = data(n, y_idx + (y * block_size), x_idx + (x * block_size), channel_idx)
return output
def _compute(*indices):
n, c, y, x = _get_indices(*indices)
return _get_pixel(n, c, y, x)
return te.compute(output_shape, _compute, name="space_to_depth", tag=tag.INJECTIVE)
+204
View File
@@ -0,0 +1,204 @@
# 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.
"""TVM operator upsampling compute."""
from tvm import te, topi
from ..utils import simplify
def upsampling(
data,
scale_h,
scale_w,
layout="NCHW",
method="nearest_neighbor",
align_corners=False,
output_shape=None,
):
"""Perform upsampling on the data.
Nearest neighbor and bilinear upsampling are supported.
Parameters
----------
data : tvm.te.Tensor
inputs is a 4-D tensor with shape
[batch, channel, in_height, in_width]
or [batch, in_height, in_width, channel]
scale_h : float
Scaling factor for height
scale_w : float
Scaling factor for width
layout : string, optional
either "NCHW" or "NHWC"
method : {"bilinear", "nearest_neighbor", "bicubic"}
Method to be used for upsampling.
output_shape: tvm_ffi.Array, optional
Shape to return. If left None will be inferred
(If shape is determined dynamically, pass out_dtype.shape as output_shape)
Returns
-------
output : tvm.te.Tensor
4-D with shape [batch, channel, in_height*scale_h, in_width*scale_w]
or [batch, in_height*scale, in_width*scale, channel]
"""
base_layout = layout[0:4]
if base_layout == "NCHW":
if not output_shape: # static case
scaled_h = data.shape[2] * scale_h
scaled_w = data.shape[3] * scale_w
reshape_size = (
simplify(topi.cast(te.round(scaled_h), data.shape[2].ty)),
simplify(topi.cast(te.round(scaled_w), data.shape[3].ty)),
)
else: # dynamic case -- we don't need to scale; already done in shape func
reshape_size = (
simplify(topi.cast(te.round(output_shape[2]), output_shape[2].ty)),
simplify(topi.cast(te.round(output_shape[3]), output_shape[3].ty)),
)
elif layout == "NHWC":
if not output_shape: # static case
scaled_h = data.shape[1] * scale_h
scaled_w = data.shape[2] * scale_w
reshape_size = (
simplify(topi.cast(te.round(scaled_h), data.shape[1].ty)),
simplify(topi.cast(te.round(scaled_w), data.shape[2].ty)),
)
else: # dynamic case
reshape_size = (
simplify(topi.cast(te.round(output_shape[1]), output_shape[1].ty)),
simplify(topi.cast(te.round(output_shape[2]), output_shape[2].ty)),
)
else:
raise ValueError(f"not support this layout {layout} yet")
coord_trans = "align_corners" if align_corners else "asymmetric"
if method[0:2] == "bi":
method = method[2:]
return topi.image.resize2d(
data,
[0.0] * 4,
reshape_size,
layout=layout,
method=method,
coordinate_transformation_mode=coord_trans,
output_shape=output_shape,
)
def upsampling3d(
data,
scale_d,
scale_h,
scale_w,
layout="NCDHW",
method="nearest_neighbor",
coordinate_transformation_mode="half_pixel",
output_shape=None,
):
"""Perform upsampling on the data.
Nearest neighbor and bilinear upsampling are supported.
Parameters
----------
data : tvm.te.Tensor
inputs is a 5-D tensor with shape
[batch, channel, in_depth, in_height, in_width]
or [batch, in_depth, in_height, in_width, channel]
scale_d : float
Scaling factor for depth
scale_h : float
Scaling factor for height
scale_w : float
Scaling factor for width
layout : string, optional
either "NCDHW" or "NDHWC"
method : {"trilinear", "nearest_neighbor"}
Method to be used for upsampling.
coordinate_transformation_mode: string, optional
Describes how to transform the coordinate in the resized tensor
to the coordinate in the original tensor.
Refer to the ONNX Resize operator specification for details.
Available options are "half_pixel", "align_corners" and "asymmetric".
output_shape: tvm_ffi.Array, optional
Shape to return. If left None will be inferred
(If shape is determined dynamically, pass out_dtype.shape as output_shape)
Returns
-------
output : tvm.te.Tensor
5-D with shape [batch, channel, in_depth*scale, in_height*scale, in_width*scale]
or [batch, in_depth*scale, in_height*scale, in_width*scale, channel]
"""
base_layout = layout[0:5]
if base_layout == "NCDHW":
if not output_shape: # static case
scaled_d = data.shape[2] * scale_d
scaled_h = data.shape[3] * scale_h
scaled_w = data.shape[4] * scale_w
resize_shape = (
simplify(topi.cast(te.round(scaled_d), data.shape[2].ty)),
simplify(topi.cast(te.round(scaled_h), data.shape[3].ty)),
simplify(topi.cast(te.round(scaled_w), data.shape[4].ty)),
)
else: # dynamic case -- don't need to scale; already done in shape func
resize_shape = (
simplify(topi.cast(te.round(output_shape[2]), data.shape[2].ty)),
simplify(topi.cast(te.round(output_shape[3]), data.shape[3].ty)),
simplify(topi.cast(te.round(output_shape[4]), data.shape[4].ty)),
)
elif layout == "NDHWC":
if not output_shape: # static case
scaled_d = data.shape[1] * scale_d
scaled_h = data.shape[2] * scale_h
scaled_w = data.shape[3] * scale_w
resize_shape = (
simplify(topi.cast(te.round(scaled_d), data.shape[1].ty)),
simplify(topi.cast(te.round(scaled_h), data.shape[2].ty)),
simplify(topi.cast(te.round(scaled_w), data.shape[3].ty)),
)
else: # dynamic case
resize_shape = (
simplify(topi.cast(te.round(output_shape[1]), data.shape[1].ty)),
simplify(topi.cast(te.round(output_shape[2]), data.shape[2].ty)),
simplify(topi.cast(te.round(output_shape[3]), data.shape[3].ty)),
)
else:
raise ValueError(f"not support this layout {layout} yet")
if method[0:3] == "tri":
method = method[3:]
return topi.image.resize3d(
data,
[0.0] * 6,
resize_shape,
layout=layout,
method=method,
coordinate_transformation_mode=coordinate_transformation_mode,
)
+310
View File
@@ -0,0 +1,310 @@
# 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=invalid-name, unused-variable
"""NN operator common utilities"""
import tvm
from ..utils import get_const_int
def infer_pad(data, data_pad):
"""Infer the padding from stages in reverse.
Parameters
----------
data : Tensor
data stage.
data_pad : Tensor
pad stage.
Returns
-------
hpad : int
padding size on height
wpad : int
padding size on width
"""
if data_pad is None:
return 0, 0
_, _, IH, IW = data.shape
_, _, TH, TW = data_pad.shape
hpad = (TH - IH) // 2
wpad = (TW - IW) // 2
return get_const_int(hpad), get_const_int(wpad)
def infer_pad3d(data, data_pad, layout):
"""Infer the padding from stages in reverse.
Parameters
----------
data : Tensor
data stage.
data_pad : Tensor
pad stage.
Returns
-------
dpad : int
padding depth
hpad : int
padding height
wpad : int
padding width
"""
if data_pad is None:
return 0, 0, 0
if layout == "NDHWC":
_, ID, IH, IW, _ = data.shape
_, TD, TH, TW, _ = data_pad.shape
elif layout == "NCDHW":
_, _, ID, IH, IW = data.shape
_, _, TD, TH, TW = data_pad.shape
else:
raise ValueError(f"Layout {layout} is not supported")
dpad = TD - ID
hpad = TH - IH
wpad = TW - IW
return get_const_int(dpad), get_const_int(hpad), get_const_int(wpad)
def infer_stride(data, kernel, out):
"""Infer the stride from stages in reverse.
Parameters
----------
data : Tensor
data stage.
kernel : Tensor
kernel stage.
out : Tensor
output stage.
Returns
-------
hstride : int
stride size on height
wstride : int
stride size on width
"""
_, _, IH, IW = data.shape
_, _, KH, KW = kernel.shape
_, _, OH, OW = out.shape
hstride = (IH - KH) // tvm.te.max(OH - 1, 1) + tvm.tirx.Select(OH == 1, 1, 0)
wstride = (IW - KW) // tvm.te.max(OW - 1, 1) + tvm.tirx.Select(OW == 1, 1, 0)
return get_const_int(hstride), get_const_int(wstride)
def get_pad_tuple(padding, kernel):
"""Common code to get the pad option
Parameters
----------
padding : int or str
Padding size, or ['VALID', 'SAME']
kernel : tuple of int
Conv kernel size
Returns
-------
pad_top : int
Padding size on top
pad_left : int
Padding size on left
pad_down : int
Padding size on down.
pad_right : int
Padding size on right.
"""
# compute the padding size
if isinstance(padding, tuple | list):
if len(padding) == 2:
pad_h = padding[0] * 2
pad_w = padding[1] * 2
elif len(padding) == 4:
return padding[0], padding[1], padding[2], padding[3]
else:
raise ValueError("Size of padding can only be 2 or 4")
elif isinstance(padding, int):
pad_h = pad_w = padding * 2
elif padding == "VALID":
pad_h = 0
pad_w = 0
elif padding == "SAME":
pad_h = kernel[0] - 1
pad_w = kernel[1] - 1
else:
raise ValueError(f"Unknown padding option {padding}")
pad_top = (pad_h + 1) // 2
pad_left = (pad_w + 1) // 2
return pad_top, pad_left, pad_h - pad_top, pad_w - pad_left
def get_pad_tuple_generic(padding, kernel):
"""Common code to get the pad option
Parameters
----------
padding : int or str
Padding size, or ['VALID', 'SAME']
kernel : tuple of int
Conv kernel size
Returns
-------
pad_top : int
Padding size on top
pad_down : int
Padding size on down.
pad_left : int
Padding size on left
pad_right : int
Padding size on right.
"""
# compute the padding size
if isinstance(padding, tuple | list):
if len(padding) == len(kernel):
pad_dimensions = [p * 2 for p in padding]
elif len(padding) == len(kernel) * 2:
return (
[padding[i] for i in range(len(kernel))],
[padding[len(kernel) + i] for i in range(len(kernel))],
)
else:
raise ValueError("Size of padding can only be len(kernel) or len(kernel) * 2")
elif isinstance(padding, int):
pad_dimensions = [padding * 2 for _ in range(len(kernel))]
elif padding == "VALID":
pad_dimensions = [0 for _ in range(len(kernel))]
elif padding == "SAME":
pad_dimensions = [k - 1 for k in kernel]
else:
raise ValueError(f"Unknown padding option {padding}")
pad_begin = [(p + 1) // 2 for p in pad_dimensions]
return [pad_begin, [pd - pb for pb, pd in zip(pad_begin, pad_dimensions)]]
def get_pad_tuple3d(padding, kernel):
"""Common code to get the pad option
Parameters
----------
padding : int or str
Padding size, or ['VALID', 'SAME']
kernel : tuple of int
Conv kernel size
Returns
-------
pad_front : int
Padding size on front.
pad_top : int
Padding size on top
pad_left : int
Padding size on left
pad_back : int
Padding size on back.
pad_down : int
Padding size on down.
pad_right : int
Padding size on right.
"""
# compute the padding size
if isinstance(padding, tuple | list):
if len(padding) == 3:
pad_d = padding[0] * 2
pad_h = padding[1] * 2
pad_w = padding[2] * 2
elif len(padding) == 6:
return padding[0], padding[1], padding[2], padding[3], padding[4], padding[5]
else:
raise ValueError("Size of padding can only be 3 or 6")
elif isinstance(padding, int):
pad_d = pad_w = pad_h = padding * 2
elif padding == "VALID":
pad_h = 0
pad_w = 0
pad_d = 0
elif padding == "SAME":
pad_d = kernel[0] - 1
pad_h = kernel[1] - 1
pad_w = kernel[2] - 1
else:
raise ValueError(f"Unknown padding option {padding}")
pad_top = (pad_h + 1) // 2
pad_left = (pad_w + 1) // 2
pad_front = (pad_d + 1) // 2
return pad_front, pad_top, pad_left, pad_d - pad_front, pad_h - pad_top, pad_w - pad_left
def get_pad_tuple1d(padding, kernel):
"""Common code to get the pad option
Parameters
----------
padding : int or str
Padding size, or ['VALID', 'SAME']
kernel : tuple of int
Conv kernel size
Returns
-------
pad_left : int
Padding size on left
pad_right : int
Padding size on right.
"""
# compute the padding size
if isinstance(padding, tuple | list):
if len(padding) == 1:
pad_w = padding[0] * 2
elif len(padding) == 2:
return padding[0], padding[1]
else:
raise ValueError("Size of padding can only be 2 or 4")
elif isinstance(padding, int):
pad_w = padding * 2
elif padding == "VALID":
pad_w = 0
elif padding == "SAME":
pad_w = kernel[0] - 1
else:
raise ValueError(f"Unknown padding option {padding}")
pad_left = (pad_w + 1) // 2
return pad_left, pad_w - pad_left
+181
View File
@@ -0,0 +1,181 @@
# 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.
#
# ruff: noqa: E731
"""Utility functions for implementing Winograd convolutions
[*] Fast Algorithms for Convolutional Neural Networks
Andrew Lavin, Scott Gray
https://arxiv.org/abs/1509.09308
https://github.com/andravin/wincnn
"""
from functools import reduce
from operator import mul
import numpy as np
from tvm.contrib.pickle_memoize import memoize
from ..utils import const_matrix
# pylint: disable=invalid-name
def _cook_toom_convolution(a, n, r):
"""Compute Cook-Toom convolution A,B,G matrices"""
def _F_m(a, n):
f = lambda j, i: reduce(mul, ((a[i] - a[k] if k != i else 1) for k in range(0, n - 1)), 1)
F = np.fromfunction(np.vectorize(f), (1, n - 1), dtype=int)
F = np.diagflat(F)
F = np.append(F, np.zeros((n - 1, 1), dtype=int), axis=1)
f = lambda i, j: 1 if j == (n - 1) else 0
z = np.fromfunction(np.vectorize(f), (1, n), dtype=int)
return np.append(F, z, axis=0)
def _A_m(a, m, n):
f = lambda i, j: a[i] ** j
A = np.fromfunction(np.vectorize(f), (m - 1, n), dtype=int)
f = lambda i, j: 1 if j == (n - 1) else 0
z = np.fromfunction(np.vectorize(f), (1, n), dtype=int)
return np.append(A, z, axis=0)
def _B_m(a, n):
f = lambda j, i: reduce(mul, ((a[i] - a[k] if k != i else 1) for k in range(0, n - 1)), 1)
Ff = np.fromfunction(np.vectorize(f), (1, n - 1), dtype=int)
f = lambda i, nth: (
(
reduce(mul, [(np.poly1d([1, -a[k]]) if k != i else 1) for k in range(0, n - 1)], 1)
).coef[n - 1 - nth - 1]
/ Ff[0, i]
)
F = np.fromfunction(np.vectorize(f), (n - 1, n - 1), dtype=int)
f = lambda i, j: -(a[i] ** (n - 1))
t = np.fromfunction(np.vectorize(f), (n - 1, 1), dtype=int)
T = np.append(np.eye(n - 1), t, axis=1)
return np.append(F.T.dot(T), np.array([np.eye(n)[n - 1]]), axis=0)
alpha = n + r - 1
f = _F_m(a, alpha)
if f[0, 0] < 0:
f[0, :] *= -1
A = _A_m(a, alpha, n)
G = _A_m(a, alpha, r).T
G = G.dot(np.linalg.inv(f)).T
B = _B_m(a, alpha)
B = B.dot(f.T)
return (A, B, G)
def _interpolation_points(degree):
"""Propose filter points"""
assert 2 < degree < 18
# Default interpolation lookup table
#
# [1] Error Analysis and Improving the Accuracy of Winograd Convolution for Deep Neural Networks
# Barbara Barabasz, Andrew Anderson, Kirk M. Soodhalter, David Gregg
# https://arxiv.org/abs/1803.10986
#
# pylint: disable=bad-whitespace,line-too-long
in_pts = [
# {invalid}
[],
# 01 {E=4.63E-08 on conv2d [1]}
[],
# 02 {E=7.65E-08 on F( 2,3) [1]}
[0, -1, 1],
# 03 {E=2.35E-07 on F( 3,3) [1]}
[0, -1, 1, 1 / 2],
# 04 {E=3.29E-07 on F( 4,3) [1]}
[0, -1, 1, 1 / 2, -2],
# 05 {E=6.81E-07 on F( 5,3) [1]}
[0, -1, 1, 1 / 2, -2, -1 / 2],
# 06 {E=8.79E-07 on F( 6,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2],
# 07 {E=3.71E-06 on F( 7,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4],
# 08 {E=7.35E-06 on F( 8,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4],
# 09 {E=2.20E-05 on F( 9,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 3 / 4, -4 / 3],
# 10 {E=3.22E-05 on F(10,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 3 / 4, -4 / 3],
# 11 {E=1.09E-04 on F(11,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 3 / 4, -4 / 3, 1 / 4],
# 12 {E=1.99E-04 on F(12,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 1 / 4, -3 / 4, 4 / 3, -4],
# 13 {E=5.54E-04 on F(13,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 1 / 4, -3 / 4, 4 / 3, 3 / 4, -4 / 3],
# 14 {E=8.80E-04 on F(14,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 1 / 4, -3 / 4, 4 / 3, -4, 3 / 4, -4 / 3],
# 15 {E=1.07E-02 on F(15,3) [1]}
[0, -1, 1, 1 / 2, -1 / 2, 2, -2, -1 / 4, 4, 1 / 4, -3 / 4, 4 / 3, -4, 2 / 3, -3 / 2, 3 / 2],
# 16 {E=1.93E-02 on F(16,3) [1]}
[
0,
-1,
1,
1 / 2,
-1 / 2,
2,
-2,
-1 / 4,
4,
1 / 4,
-3 / 4,
4 / 3,
-4,
2 / 3,
-3 / 2,
-2 / 3,
3 / 2,
],
] # pylint: enable=bad-whitespace,line-too-long
return np.array(in_pts[degree - 1], dtype=np.float64)
@memoize("topi.nn.winograd_matrices", save_at_exit=False)
def winograd_transform_matrices(tile_size, kernel_size, out_dtype):
"""Compute the A, B, and G transform matrices for `tile_size` as a `tvm.Expr`."""
if not 1 < tile_size < 9:
raise ValueError(f"Unsupported tile size for Winograd: {tile_size}")
if not 2 < kernel_size < 8:
raise ValueError(f"Unsupported kernel size for Winograd: {kernel_size}")
degree = tile_size + kernel_size - 2
intp_pts = _interpolation_points(degree)
A_data, B_data, G_data = _cook_toom_convolution(intp_pts, tile_size, kernel_size)
out_dtype = "uint16" if out_dtype == "bfloat16" else out_dtype
return (
const_matrix(A_data.astype(out_dtype), "A"),
const_matrix(B_data.astype(out_dtype), "B"),
const_matrix(G_data.astype(out_dtype), "G"),
)