blob: ac6920d5b7809826444be2aec5abaa59d9b2e085 [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.
from math import sqrt
import pytest
import tvm
from tvm import relay
from tvm.relay import transform
from tvm.relay.testing import run_opt_pass, run_infer_type
import numpy as np
def test_simplify_reshape():
def before():
x = relay.var("x", shape=(1, 16, 16, 16), dtype="float32")
w = relay.var("w", shape=(32, 16, 3, 3), dtype="float32")
y = relay.nn.conv2d(x, w, padding=(1, 1))
y = relay.reshape(y, newshape=(1, 16, -1))
y = relay.reshape(y, newshape=(4, 8, -1, 16))
y = relay.reverse_reshape(y, newshape=(32, 0, -1))
return relay.Function([x, w], y)
def expected():
x = relay.var("x", shape=(1, 16, 16, 16), dtype="float32")
w = relay.var("w", shape=(32, 16, 3, 3), dtype="float32")
y = relay.nn.conv2d(x, w, padding=(1, 1))
y = relay.reshape(y, newshape=(32, 16, 16))
return relay.Function([x, w], y)
def symbolic():
b = tvm.te.size_var("b")
x = relay.var("x", shape=(b, 16, 16, 16), dtype="float32")
w = relay.var("w", shape=(32, 16, 3, 3), dtype="float32")
y = relay.nn.conv2d(x, w, padding=(1, 1))
y = relay.reshape(y, newshape=(1, 16, -1))
y = relay.reshape(y, newshape=(4, 8, -1, 16))
y = relay.reverse_reshape(y, newshape=(32, 0, -1))
return relay.Function([x, w], y)
z = before()
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(expected(), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
z = symbolic()
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(symbolic(), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
def test_simplify_transpose():
# Test a series of transpose and layout_transform ops
def before1():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.transpose(x, axes=[0, 2, 3, 1]) # To NHWC
y = relay.layout_transform(y, "NHWC", "HWCN") # To HWCN
y = relay.transpose(y, axes=[3, 0, 1, 2]) # To NHWC
return relay.Function([x], y)
def expected1():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.transpose(x, axes=[0, 2, 3, 1]) # To NHWC
return relay.Function([x], y)
# Test that all transpose ops can be cancelled
def before2():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.nn.relu(x)
y = relay.transpose(y, axes=[0, 2, 3, 1]) # To NHWC
y = relay.transpose(y, axes=[1, 2, 3, 0]) # To HWCN
y = relay.transpose(y, axes=[3, 2, 0, 1]) # To NCHW
return relay.Function([x], y)
def expected2():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.nn.relu(x)
return relay.Function([x], y)
# Test default axis (reverse) and negative axis
def before3():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.nn.relu(x)
y = relay.transpose(y) # Reverse
y = relay.transpose(y) # Reverse
y = relay.transpose(y, axes=[0, 2, -1, 1])
y = relay.transpose(y) # Reverse
y = relay.transpose(y) # Reverse
return relay.Function([x], y)
def expected3():
x = relay.var("x", shape=(1, 3, 224, 224), dtype="float32") # NCHW
y = relay.nn.relu(x)
y = relay.transpose(y, axes=[0, 2, 3, 1])
return relay.Function([x], y)
# Test a series of transpose and rank changing layout_transform
def before4():
"""
Simplify transpose->layout_transform and its inverse.
Input:
NHWC -> NCHW -> NCHW4c -> op -> NCHW4c -> NCHW -> NHWC
Simplified:
NHWC -> NCHW4c -> op -> NCHW4c -> NHWC
"""
x = relay.var("x", shape=(1, 56, 56, 128), dtype="float32")
y = relay.transpose(x, axes=[0, 3, 1, 2])
y = relay.layout_transform(y, "NCHW", "NCHW4c")
y = relay.nn.relu(y)
y = relay.layout_transform(y, "NCHW4c", "NCHW")
y = relay.transpose(y, axes=[0, 2, 3, 1])
return relay.Function([x], y)
def expected4():
x = relay.var("x", shape=(1, 56, 56, 128), dtype="float32") # NHWC
y = relay.layout_transform(x, "NHWC", "NCHW4c") # To NCHW4c
y = relay.nn.relu(y)
y = relay.layout_transform(y, "NCHW4c", "NHWC") # To NHWC
return relay.Function([x], y)
def before5():
"""
Simplify layout_transform->layout_transform and its inverse.
Input:
NHWC -> NCHW -> NCHW4c -> op -> NCHW4c -> NCHW -> NHWC
Simplified:
NHWC -> NCHW4c -> op -> NCHW4c -> NHWC
"""
x = relay.var("x", shape=(1, 56, 56, 128), dtype="float32") # NHWC
y = relay.layout_transform(x, "NHWC", "NCHW") # To NCHW
y = relay.layout_transform(y, "NCHW", "NCHW4c") # To NCHW4c
y = relay.nn.relu(y)
y = relay.layout_transform(y, "NCHW4c", "NCHW") # To NCHW
y = relay.layout_transform(y, "NCHW", "NHWC") # To NHWC
return relay.Function([x], y)
def expected5():
x = relay.var("x", shape=(1, 56, 56, 128), dtype="float32") # NHWC
y = relay.layout_transform(x, "NHWC", "NCHW4c") # To NCHW4c
y = relay.nn.relu(y)
y = relay.layout_transform(y, "NCHW4c", "NHWC") # To NHWC
return relay.Function([x], y)
def before6():
"""
Remove trivial layout_transform->layout_transform.
Input:
NCHW -> NHWC -> NCHW -> op
Simplified:
NHWC -> op
"""
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.layout_transform(x, "NCHW", "NHWC")
y = relay.layout_transform(y, "NHWC", "NCHW")
y = relay.nn.relu(y)
return relay.Function([x], y)
def expected6():
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.nn.relu(x)
return relay.Function([x], y)
def before7():
"""
Remove trivial layout_transform->layout_transform.
Input:
NCHW4c -> NCHW8c -> NCHW4c -> op
Simplified:
NCHW4c -> op
"""
x = relay.var("x", shape=(1, 32, 56, 56, 4), dtype="float32")
y = relay.layout_transform(x, "NCHW4c", "NCHW8c")
y = relay.layout_transform(y, "NCHW8c", "NCHW4c")
y = relay.nn.relu(y)
return relay.Function([x], y)
def expected7():
x = relay.var("x", shape=(1, 32, 56, 56, 4), dtype="float32")
y = relay.nn.relu(x)
return relay.Function([x], y)
def before8():
"""
Simplify layout_transform->layout_transform with rank contraction and expansion
Input:
NCHW4c -> NCHW -> NCHW8c -> op
Simplified:
NCHW4c -> NCHW8c -> op
"""
x = relay.var("x", shape=(1, 32, 56, 56, 4), dtype="float32")
y = relay.layout_transform(x, "NCHW4c", "NCHW")
y = relay.layout_transform(y, "NCHW", "NCHW8c")
y = relay.nn.relu(y)
return relay.Function([x], y)
def expected8():
x = relay.var("x", shape=(1, 32, 56, 56, 4), dtype="float32")
y = relay.layout_transform(x, "NCHW4c", "NCHW8c")
y = relay.nn.relu(y)
return relay.Function([x], y)
def before9():
"""
Remove trivial layout_transform->layout_transform.
Input:
NCHW -> NCHW4c -> NCHW -> op
Simplified:
NCHW -> op
"""
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.layout_transform(x, "NCHW", "NCHW4c")
y = relay.layout_transform(y, "NCHW4c", "NCHW")
y = relay.nn.relu(y)
return relay.Function([x], y)
def expected9():
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.nn.relu(x)
return relay.Function([x], y)
def before10():
"""
Simplify layout_transform->layout_transform without rank change to transpose.
Input:
NCHW -> NHWC -> CHWN -> op
Simplified:
NCHW -> CHWN -> op
"""
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.layout_transform(x, "NCHW", "NHWC")
y = relay.layout_transform(y, "NHWC", "CHWN")
y = relay.nn.relu(y)
return relay.Function([x], y)
def expected10():
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.transpose(x, axes=[1, 2, 3, 0])
y = relay.nn.relu(y)
return relay.Function([x], y)
def before11():
"""
Remove trivial no op transpose ops
Input:
op1 -> relay.transpose(x, axes=[0, 1, 2, 3]) -> op2
Simplified:
op1 -> op2
"""
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.transpose(x, axes=[0, 1, 2, 3])
y = relay.nn.relu(y)
y = relay.layout_transform(y, "NCHW", "NCHW")
return relay.Function([x], y)
def expected11():
x = relay.var("x", shape=(1, 128, 56, 56), dtype="float32")
y = relay.nn.relu(x)
return relay.Function([x], y)
for before, expected in [
[before1(), expected1()],
[before2(), expected2()],
[before3(), expected3()],
[before4(), expected4()],
[before5(), expected5()],
[before6(), expected6()],
[before7(), expected7()],
[before8(), expected8()],
[before9(), expected9()],
[before10(), expected10()],
[before11(), expected11()],
]:
after = run_opt_pass(before, transform.SimplifyExpr())
expected = run_opt_pass(expected, transform.InferType())
assert tvm.ir.structural_equal(after, expected), "\nafter: {} \nexpected: {}".format(
after, expected
)
def test_simplify_full_elementwise():
def validate(shape, value, dtype):
def before_left(x, elem_op, full):
return elem_op(full, x)
def after_left(x, elem_op, value):
if elem_op == relay.add and value == 0:
return x
elif elem_op == relay.multiply and (value == 1 or (value > 1 and dtype == "bool")):
return x
return elem_op(relay.const(value, dtype), x)
def before_right(x, elem_op, full):
return elem_op(x, full)
def after_right(x, elem_op, value):
if elem_op in [relay.add, relay.subtract] and value == 0:
return x
elif elem_op in [relay.multiply, relay.divide] and (
value == 1 or (value > 1 and dtype == "bool")
):
return x
return elem_op(x, relay.const(value, dtype))
x = relay.var("x", shape=shape, dtype=dtype)
elem_ops = [relay.add, relay.multiply, relay.subtract, relay.divide]
full_ops = []
if value == 0:
full_ops.append(relay.zeros(shape, dtype))
full_ops.append(relay.zeros_like(x))
if value == 1:
full_ops.append(relay.ones(shape, dtype))
full_ops.append(relay.ones_like(x))
else:
full_ops.append(relay.full(relay.const(value, dtype), shape))
full_ops.append(relay.full_like(x, relay.const(value, dtype)))
for op in elem_ops:
for full in full_ops:
z = before_left(x, op, full)
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(after_left(x, op, value), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
z = before_right(x, op, full)
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(after_right(x, op, value), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
# Test the case in which x is broadcast to full's shape
full_ops = []
if value == 0:
full_ops.append(relay.zeros(shape * 2, dtype))
if value == 1:
full_ops.append(relay.ones(shape * 2, dtype))
else:
full_ops.append(relay.full(relay.const(value, dtype), shape * 2))
for op in elem_ops:
for full in full_ops:
z = before_left(x, op, full)
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(before_left(x, op, full), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
z = before_right(x, op, full)
zz = run_opt_pass(z, transform.SimplifyExpr())
after = run_opt_pass(before_right(x, op, full), transform.InferType())
assert tvm.ir.structural_equal(zz, after)
for shape in [[10], [10, 10], [10, 10, 10]]:
for dtype in ["float32", "int32", "bool"]:
for value in [0, 1, 2]:
validate(shape, value, dtype)
def test_eliminate_identity():
def check(x, y=None, do_nothing=False):
expected = run_infer_type(x)
if do_nothing:
actual = run_opt_pass(x, transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
else:
assert y is not None
actual = run_opt_pass(y, transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
shape = [2, 3, 4]
dtype = "float32"
x = relay.var("x", shape=shape, dtype=dtype)
x = run_opt_pass(x, transform.InferType())
for op, op_like, id_op, const in [
(relay.zeros, relay.zeros_like, relay.add, relay.const(0, dtype)),
(relay.ones, relay.ones_like, relay.multiply, relay.const(1, dtype)),
]:
check(x, id_op(op_like(x), x))
check(x, id_op(op(shape, dtype), x))
check(x, id_op(const, x))
check(x, id_op(op(shape[1:], dtype), x))
check(x, id_op(x, op_like(x)))
check(x, id_op(x, op(shape, dtype)))
check(x, id_op(x, const))
check(x, id_op(x, op(shape[1:], dtype)))
check(id_op(x, op([2] + shape, dtype)), do_nothing=True)
check(id_op(op([2] + shape, dtype), x), do_nothing=True)
for op, op_like, id_op, const in [
(relay.zeros, relay.zeros_like, relay.subtract, relay.const(0, dtype)),
(relay.ones, relay.ones_like, relay.divide, relay.const(1, dtype)),
]:
check(x, id_op(x, op_like(x)))
check(x, id_op(x, const))
check(x, id_op(x, op(shape, dtype)))
check(x, id_op(x, op(shape[1:], dtype)))
check(id_op(x, op([2] + shape, dtype)), do_nothing=True)
check(id_op(const, x), id_op(op(shape, dtype), x))
check(id_op(const, x), id_op(op_like(x), x))
def test_simplify_same_cast():
dtype = "int32"
data = relay.var("data", shape=(3, 4, 5), dtype=dtype)
expr1 = relay.cast(data, dtype)
dtype_like = relay.var("dtype_like", shape=(2, 2, 2), dtype=dtype)
expr2 = relay.cast_like(data, dtype_like)
expected = run_infer_type(data)
actual1 = run_opt_pass(expr1, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual1, expected)
actual2 = run_opt_pass(expr2, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual2, expected)
def test_simplify_consecutive_cast():
x = relay.var("x", shape=(3, 4, 5), dtype="int8")
y = relay.var("y", shape=(3, 4), dtype="int64")
z = relay.var("z", shape=(3,), dtype="float32")
expr1 = relay.cast(x, "int16")
expr2 = relay.cast(expr1, "int32")
expr3 = relay.cast_like(expr2, y)
expr4 = relay.cast_like(expr3, z)
actual1 = run_opt_pass(expr2, relay.transform.SimplifyExpr())
expected = run_infer_type(relay.cast(x, "int32"))
assert tvm.ir.structural_equal(actual1, expected)
actual2 = run_opt_pass(expr3, relay.transform.SimplifyExpr())
expected = run_infer_type(relay.cast(x, "int64"))
assert tvm.ir.structural_equal(actual2, expected)
actual3 = run_opt_pass(expr4, relay.transform.SimplifyExpr())
expected = run_infer_type(relay.cast(x, "float32"))
assert tvm.ir.structural_equal(actual3, expected)
# cannot simplify the narrow cast
x = relay.var("x", shape=(3, 4, 5), dtype="float32")
y = relay.var("y", shape=(3, 4), dtype="float32")
expr1 = relay.cast(x, "int32")
expr2 = relay.cast_like(expr1, y)
actual = run_opt_pass(expr2, relay.transform.SimplifyExpr())
expected = run_infer_type(relay.cast(expr1, "float32"))
assert tvm.ir.structural_equal(actual, expected)
x = relay.var("x", shape=(3, 4), dtype="int64")
expr1 = relay.cast(x, "bool")
expr2 = relay.cast(expr1, "int32")
actual = run_opt_pass(expr2, relay.transform.SimplifyExpr())
expected = run_infer_type(expr2)
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_reshape_like():
data = relay.var("data", shape=(2, 3, 4), dtype="float32")
shape_like = relay.var("shape_like", shape=(6, 2, 2), dtype="float32")
expr = relay.reshape_like(data, shape_like)
expected = run_infer_type(relay.reshape(data, (6, 2, 2)))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_reshape_like_attrs():
data = relay.var("data", shape=(2, 3, 4), dtype="float32")
shape_like = relay.var("shape_like", shape=(6, 2, 2), dtype="float32")
expr = relay.reshape_like(data, shape_like, lhs_begin=2, rhs_begin=1)
expected = run_infer_type(relay.reshape(data, (2, 3, 2, 2)))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_zeros_like():
dtype = "int32"
shape_like = relay.var("shape_like", shape=(3, 4, 5), dtype=dtype)
expr = relay.zeros_like(shape_like)
expected = run_infer_type(relay.zeros((3, 4, 5), dtype))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_ones_like():
dtype = "int32"
shape_like = relay.var("shape_like", shape=(3, 4, 5), dtype=dtype)
expr = relay.ones_like(shape_like)
expected = run_infer_type(relay.ones((3, 4, 5), dtype))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_full_like():
dtype = "int32"
shape_like = relay.var("shape_like", shape=(3, 4, 5), dtype=dtype)
fill_value = relay.var("fill", relay.TensorType((), "float32"))
expr = relay.full_like(shape_like, fill_value)
expected = run_infer_type(relay.full(fill_value, (3, 4, 5), dtype))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_collapse_sum_like():
data = relay.var("data", shape=(3, 3, 3), dtype="float32")
shape_like = relay.var("shape_like", shape=(3,), dtype="float32")
expr = relay.collapse_sum_like(data, shape_like)
expected = run_infer_type(relay.collapse_sum_to(data, (3,)))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_broadcast_to_like():
data = relay.var("data", shape=(3,), dtype="float32")
shape_like = relay.var("shape_like", shape=(3, 3, 3), dtype="float32")
expr = relay.broadcast_to_like(data, shape_like)
expected = run_infer_type(relay.broadcast_to(data, (3, 3, 3)))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_cast_like():
dim_any = tvm.tir.Any()
data = relay.var("data", shape=(3, dim_any, 5), dtype="float32")
dtype_like = relay.var("dtype_like", shape=(dim_any, 3, 3), dtype="int32")
expr = relay.cast_like(data, dtype_like)
expected = run_infer_type(relay.cast(data, "int32"))
actual = run_opt_pass(expr, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_concretize_multiple():
x = relay.var("x", shape=(2, 3), dtype="float32")
y = relay.var("y", shape=(3,), dtype="float32")
l = x + y
dl = relay.ones_like(l)
dx = relay.zeros_like(x)
dy = relay.zeros_like(y)
dx = dx + relay.collapse_sum_like(dl, dx)
dy = dy + relay.collapse_sum_like(dl, dy)
ret = relay.Tuple([dx, dy])
dl_c = relay.ones((2, 3), "float32")
# NOTE: these are removed by EliminateIdentity
# dx_c = relay.zeros((2, 3), "float32")
# dy_c = relay.zeros((3,), "float32")
dx_c = relay.collapse_sum_to(dl_c, (2, 3))
dy_c = relay.collapse_sum_to(dl_c, (3,))
ret_c = relay.Tuple([dx_c, dy_c])
expected = run_infer_type(ret_c)
actual = run_opt_pass(ret, relay.transform.SimplifyExpr())
assert tvm.ir.structural_equal(actual, expected)
def test_simplify_mul_add():
def check_simple_fold(origin_exprs, expect_expr):
for origin_expr in origin_exprs:
simple_expr = run_opt_pass(origin_expr, transform.SimplifyExpr())
assert tvm.ir.structural_equal(simple_expr, expect_expr)
n = 32
c1_val = np.random.uniform(size=n).astype("float32")
c2_val = np.random.uniform(size=n).astype("float32")
c3_val = np.random.uniform(size=n).astype("float32")
x = relay.var("x", shape=(n,), dtype="float32")
c1 = relay.const(c1_val)
c2 = relay.const(c2_val)
c3 = relay.const(c3_val)
# add-add -> add
origin_exprs = [
x + c1 + c2,
c1 + x + c2,
]
expect_expr = x + relay.const(c1_val + c2_val)
check_simple_fold(origin_exprs, expect_expr)
# mul-mul -> mul
origin_exprs = [
x * c1 * c2,
c1 * x * c2,
]
expect_expr = x * relay.const(c1_val * c2_val)
check_simple_fold(origin_exprs, expect_expr)
# add-mul -> mul-add
origin_exprs = [
(x + c1) * c2,
(c1 + x) * c2,
c2 * (x + c1),
c2 * (c1 + x),
]
expect_expr = x * c2 + relay.const(c1_val * c2_val)
check_simple_fold(origin_exprs, expect_expr)
# add-mul-add -> mul-add
origin_exprs = [
(x + c1) * c2 + c3,
(c1 + x) * c2 + c3,
c2 * (x + c1) + c3,
c2 * (c1 + x) + c3,
c3 + (x + c1) * c2,
c3 + (c1 + x) * c2,
c3 + c2 * (x + c1),
c3 + c2 * (c1 + x),
]
expect_expr = x * c2 + relay.const(c1_val * c2_val + c3_val)
check_simple_fold(origin_exprs, expect_expr)
# mul-add-mul -> mul-add
origin_exprs = [
(x * c1 + c2) * c3,
(c1 * x + c2) * c3,
(c2 + x * c1) * c3,
(c2 + c1 * x) * c3,
c3 * (x * c1 + c2),
c3 * (c1 * x + c2),
c3 * (c2 + x * c1),
c3 * (c2 + c1 * x),
]
expect_expr = x * relay.const(c1_val * c3_val) + relay.const(c2_val * c3_val)
check_simple_fold(origin_exprs, expect_expr)
def test_simplify_rsqrt():
shape = (32, 1, 1)
x = relay.var("x", shape=shape, dtype="float32")
def before(c):
return relay.const(c) / relay.sqrt(x)
def expected(c):
if c == 1:
return relay.rsqrt(x)
else:
return relay.const(c) * relay.rsqrt(x)
for c in [1.0, 2.0, 2.5]:
opt = run_opt_pass(before(c), transform.SimplifyExpr())
after = run_opt_pass(expected(c), transform.InferType())
assert tvm.ir.structural_equal(opt, after)
def test_simplify_dq_argmax():
shape = (4, 32, 1, 1)
x = relay.var("x", shape=shape, dtype="int8")
def before():
y = relay.qnn.op.dequantize(x, relay.const(2.0), relay.const(0))
return relay.op.argmax(y, axis=1)
def expected():
return relay.op.argmax(x, axis=1)
opt = run_opt_pass(before(), transform.SimplifyExpr())
after = run_opt_pass(expected(), transform.InferType())
assert tvm.ir.structural_equal(opt, after)
def test_simplify_dq_argmin():
shape = (4, 32, 1, 1)
x = relay.var("x", shape=shape, dtype="int8")
def before():
y = relay.qnn.op.dequantize(x, relay.const(2.0), relay.const(0))
return relay.op.argmin(y, axis=1)
def expected():
return relay.op.argmin(x, axis=1)
opt = run_opt_pass(before(), transform.SimplifyExpr())
after = run_opt_pass(expected(), transform.InferType())
assert tvm.ir.structural_equal(opt, after)
def test_simplify_dq_argsort():
shape = (4, 32, 1, 1)
x = relay.var("x", shape=shape, dtype="int8")
def before():
y = relay.qnn.op.dequantize(x, relay.const(2.0), relay.const(0))
return relay.op.argsort(y, axis=1)
def expected():
return relay.op.argsort(x, axis=1)
opt = run_opt_pass(before(), transform.SimplifyExpr())
after = run_opt_pass(expected(), transform.InferType())
assert tvm.ir.structural_equal(opt, after)
def test_simplify_clip_cast():
def before1():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "uint8")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
def expected1():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
return relay.Function([x], clip)
def before2():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "uint8")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
def expected2():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
return relay.Function([x], clip)
def before3():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "uint8")
cast = relay.cast(cast, "int16")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
def expected3():
x = relay.var("x", shape=(4, 8), dtype="int32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
return relay.Function([x], clip)
def before4():
x = relay.var("x", shape=(4, 8), dtype="float32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "uint8")
cast = relay.cast(cast, "int16")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
def expected4():
x = relay.var("x", shape=(4, 8), dtype="float32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "int32")
return relay.Function([x], cast)
def before5():
x = relay.var("x", shape=(4, 8), dtype="float32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "int8")
cast = relay.cast(cast, "int16")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
def expected5():
x = relay.var("x", shape=(4, 8), dtype="float32")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
cast = relay.cast(clip, "int8")
cast = relay.cast(cast, "int32")
return relay.Function([x], cast)
for before, expected in [
[before1(), expected1()],
[before2(), expected2()],
[before3(), expected3()],
[before4(), expected4()],
[before5(), expected5()],
]:
after = run_opt_pass(before, transform.SimplifyExpr())
expected = run_opt_pass(expected, transform.InferType())
assert tvm.ir.structural_equal(after, expected), "\nafter: {} \nexpected: {}".format(
after, expected
)
def test_simplify_cast_clip():
def before1():
x = relay.var("x", shape=(4, 8), dtype="int32")
cast = relay.cast(x, "uint8")
clip = relay.clip(cast, a_min=0.0, a_max=255.0)
return relay.Function([x], clip)
def expected1():
x = relay.var("x", shape=(4, 8), dtype="int32")
cast = relay.cast(x, "uint8")
return relay.Function([x], cast)
def before2():
x = relay.var("x", shape=(4, 8), dtype="uint8")
clip = relay.clip(x, a_min=0.0, a_max=255.0)
return relay.Function([x], clip)
def expected2():
x = relay.var("x", shape=(4, 8), dtype="uint8")
return relay.Function([x], x)
def before3():
x = relay.var("x", shape=(4, 8), dtype="float32")
cast = relay.cast(x, "bfloat16")
clip = relay.clip(cast, a_min=-0.2, a_max=0.4)
return relay.Function([x], clip)
def expected3():
x = relay.var("x", shape=(4, 8), dtype="float32")
cast = relay.cast(x, "bfloat16")
clip = relay.clip(cast, a_min=-0.2, a_max=0.4)
return relay.Function([x], clip)
for before, expected in [
[before1(), expected1()],
[before2(), expected2()],
[before3(), expected3()],
]:
after = run_opt_pass(before, transform.SimplifyExpr())
expected = run_opt_pass(expected, transform.InferType())
assert tvm.ir.structural_equal(after, expected), "\nafter: {} \nexpected: {}".format(
after, expected
)
def test_simplify_add():
x = relay.var("x", shape=(1, 3, 100, 100), dtype="float32")
def before():
return relay.add(x, x)
def expected():
s = relay.const(2.0)
return relay.multiply(x, s)
opt = run_opt_pass(before(), transform.SimplifyExpr())
ref = run_infer_type(expected())
assert tvm.ir.structural_equal(opt, ref)
def test_binomials():
def check_simple_fold(origin_exprs, expect_exprs):
for origin_expr in origin_exprs:
simple_expr = run_opt_pass(origin_expr, transform.SimplifyExpr())
match = False
for expected in expect_exprs:
e = run_opt_pass(expected, transform.EliminateCommonSubexpr())
match = match or tvm.ir.structural_equal(simple_expr, e)
if match:
break
assert match
def gen_expected_expressions(x, y, a, b, c, dtype):
if c == 1 and a > 1:
swap = a
a = c
c = swap
swap = x
x = y
y = swap
det = b * b - 4 * a * c
if det < 0:
return gen_expressions(x, y, a, b, c)
p_val = (b + sqrt(det)) / (2 * a)
q_val = (b - sqrt(det)) / (2 * a)
p = relay.const(p_val, dtype=dtype)
q = relay.const(q_val, dtype=dtype)
first_exp = [x + y, y + x] if p_val == 1 else [x + p * y, p * y + x, x + y * p, y * p + x]
second_exp = [x + y, y + x] if q_val == 1 else [x + q * y, q * y + x, x + y * q, y * q + x]
final_exp = []
for f in first_exp:
for s in second_exp:
final_exp.append(f * s)
if not p_val == q_val:
final_exp.append(s * f)
return final_exp
def gen_expressions(x, y, a, b, c):
first_exp = [x * x] if a == 1 else [a * x * x, x * a * x, x * x * a]
second_exp = (
[x * y, y * x]
if b == 1
else [b * x * y, x * b * y, x * y * b, b * y * x, y * b * x, y * x * b]
)
third_exp = [y * y] if c == 1 else [c * y * y, y * c * y, y * y * c]
final_exp = []
for f in first_exp:
for s in second_exp:
for t in third_exp:
final_exp.append(f + s + t)
final_exp.append(f + t + s)
final_exp.append(s + f + t)
final_exp.append(s + t + f)
final_exp.append(t + f + s)
final_exp.append(t + s + f)
return final_exp
n = 5
dtypes = ["int32", "float32", "float64"]
for dtype in dtypes:
x = relay.var("x", shape=(n,), dtype=dtype)
y = relay.var("y", shape=(n,), dtype=dtype)
a = relay.const(1, dtype=dtype)
b = relay.const(2, dtype=dtype)
c = relay.const(1, dtype=dtype)
origin_exprs = gen_expressions(x, y, a, b, c)
expect_expr = gen_expected_expressions(x, y, 1, 2, 1, dtype)
check_simple_fold(origin_exprs, expect_expr)
a = relay.const(6, dtype=dtype)
b = relay.const(5, dtype=dtype)
c = relay.const(1, dtype=dtype)
origin_exprs = gen_expressions(x, y, a, b, c)
expect_expr = gen_expected_expressions(x, y, 6, 5, 1, dtype)
check_simple_fold(origin_exprs, expect_expr)
a = relay.const(1, dtype=dtype)
b = relay.const(1, dtype=dtype)
c = relay.const(1, dtype=dtype)
origin_exprs = gen_expressions(x, y, a, b, c)
expect_expr = gen_expected_expressions(x, y, 1, 1, 1, dtype)
check_simple_fold(origin_exprs, expect_expr)
a = relay.const(1, dtype=dtype)
b = relay.const(4, dtype=dtype)
c = relay.const(4, dtype=dtype)
origin_exprs = gen_expressions(x, y, a, b, c)
expect_expr = gen_expected_expressions(x, y, 1, 4, 4, dtype)
check_simple_fold(origin_exprs, expect_expr)
if __name__ == "__main__":
tvm.testing.main()