Files
apache--tvm/python/tvm/topi/nn/pad.py
T
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

317 lines
9.9 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""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)