376 lines
10 KiB
Python
376 lines
10 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.
|
|
# ruff: noqa: F811
|
|
import numpy as np
|
|
import pytest
|
|
import tvm_ffi
|
|
|
|
import tvm
|
|
from tvm import relax as rx
|
|
from tvm import tirx
|
|
from tvm.relax.expr import make_shape
|
|
from tvm.script import relax as R
|
|
|
|
|
|
def _check_equal(x, y, map_free_vars=False):
|
|
tvm.ir.assert_structural_equal(x, y, map_free_vars)
|
|
tvm.ir.assert_structural_equal(y, x, map_free_vars)
|
|
|
|
xhash = tvm_ffi.structural_hash(x, map_free_vars)
|
|
yhash = tvm_ffi.structural_hash(y, map_free_vars)
|
|
|
|
assert xhash == yhash
|
|
|
|
|
|
def _check_json_roundtrip(x):
|
|
xret = tvm.ir.load_json(tvm.ir.save_json(x))
|
|
_check_equal(x, xret, map_free_vars=True)
|
|
return xret
|
|
|
|
|
|
def _check_type_missing(ty):
|
|
assert isinstance(ty, tvm.ir.Type)
|
|
assert ty.is_missing()
|
|
|
|
|
|
def test_var() -> None:
|
|
v0 = rx.Var("v0")
|
|
assert v0.name_hint == "v0"
|
|
_check_type_missing(v0.ty)
|
|
shape = [54, 96]
|
|
v1 = rx.Var("v1", R.Tensor(shape, "float32"))
|
|
assert v1.name_hint == "v1"
|
|
for s0, s1 in zip(v1.ty.shape, shape):
|
|
assert s0 == s1
|
|
tvm.ir.assert_structural_equal(v1.ty, rx.TensorType(shape, "float32"))
|
|
|
|
|
|
def test_tensor_type_empty_dtype_is_unknown() -> None:
|
|
tvm.ir.assert_structural_equal(rx.TensorType([1, 2], dtype=""), rx.TensorType([1, 2], None))
|
|
|
|
|
|
def test_relax_expr_ty_running_example() -> None:
|
|
m = tirx.Var("m", "int64")
|
|
x = rx.Var("x", R.Tensor([m, 16], "float32"))
|
|
|
|
assert isinstance(x.ty, tvm.ir.Type)
|
|
assert x.ty.dtype == "float32"
|
|
assert x.ty.ndim == 2
|
|
|
|
call = rx.op.add(x, x)
|
|
_check_type_missing(call.ty)
|
|
|
|
bb = rx.BlockBuilder()
|
|
normalized = bb.normalize(call)
|
|
|
|
assert isinstance(normalized.ty, tvm.ir.Type)
|
|
tvm.ir.assert_structural_equal(normalized.ty, x.ty)
|
|
|
|
|
|
def test_dataflow_var() -> None:
|
|
v0 = rx.DataflowVar("v0")
|
|
assert v0.name_hint == "v0"
|
|
_check_type_missing(v0.ty)
|
|
|
|
shape = [54, 96]
|
|
v1 = rx.DataflowVar("v1", R.Tensor(shape, "float16"))
|
|
assert v1.name_hint == "v1"
|
|
|
|
assert isinstance(v1, rx.DataflowVar)
|
|
tvm.ir.assert_structural_equal(v1.ty, rx.TensorType(shape, "float16"))
|
|
|
|
|
|
def test_tuple() -> None:
|
|
v0 = rx.Var("v0")
|
|
v1 = rx.Var("v1")
|
|
t = rx.Tuple((v0, v1))
|
|
|
|
assert t.fields[0] == v0
|
|
assert t.fields[1] == v1
|
|
assert t[0] == v0
|
|
assert t[1] == v1
|
|
assert t[-1] == v1
|
|
assert t[-2] == v0
|
|
|
|
with pytest.raises(IndexError, match="Tuple index out of range"):
|
|
t[2]
|
|
|
|
with pytest.raises(IndexError, match="Tuple index out of range"):
|
|
t[-3]
|
|
|
|
|
|
def test_tuple_ty_inferred_on_construction():
|
|
v0 = rx.Var("v0", rx.AnyType())
|
|
v1 = rx.Var("v1", rx.AnyType())
|
|
tup = rx.Tuple((v0, v1))
|
|
|
|
assert tup.ty is not None
|
|
tvm.ir.assert_structural_equal(tup.ty, rx.TupleType([rx.AnyType(), rx.AnyType()]))
|
|
|
|
|
|
def test_tuple_ty_requires_fields_with_known_ty():
|
|
v0 = rx.Var("v0", rx.AnyType())
|
|
v1 = rx.Var("v1")
|
|
tup = rx.Tuple((v0, v1))
|
|
|
|
_check_type_missing(tup.ty)
|
|
|
|
|
|
def test_match_cast() -> None:
|
|
# match_cast([16, 8], [m, n])
|
|
m = tirx.Var("m", dtype="int64")
|
|
n = tirx.Var("n", dtype="int64")
|
|
shape = rx.const([16, 8], "int32")
|
|
var = rx.Var("v0", R.Shape())
|
|
b0 = rx.MatchCast(var, shape, R.Tensor([m, n], "int32"))
|
|
assert b0.value == shape
|
|
assert b0.pattern[0] == m
|
|
assert b0.pattern[1] == n
|
|
assert b0.var is not None
|
|
|
|
# var1: R.Tensor((m, n), "float32") =
|
|
# match_cast(var0: R.Tensor("float32", ndim=-1), R.Tensor((m, n), "float32"))
|
|
value = rx.Var("value", R.Tensor("float32", ndim=-1))
|
|
|
|
var = rx.Var("v1", R.Tensor([m, n], "float32"))
|
|
b1 = rx.MatchCast(var, value, R.Tensor([m, n], "float32"))
|
|
assert b1.value == value
|
|
assert b1.pattern[0] == m
|
|
assert b1.pattern[1] == n
|
|
assert b1.var is not None
|
|
|
|
|
|
def test_match_cast() -> None:
|
|
m = tirx.Var("m", dtype="int64")
|
|
n = tirx.Var("n", dtype="int64")
|
|
ivalue = rx.Var("input_value")
|
|
ty = rx.TensorType([n, m], "float32")
|
|
b0 = rx.MatchCast(rx.Var("v"), ivalue, ty)
|
|
assert b0.value.same_as(ivalue)
|
|
assert b0.ty == ty
|
|
_check_json_roundtrip(b0)
|
|
|
|
|
|
def test_var_binding() -> None:
|
|
v0 = rx.Var("v0")
|
|
val = rx.const(np.random.rand(24, 56))
|
|
b0 = rx.VarBinding(v0, val)
|
|
assert b0.var.name_hint == "v0"
|
|
assert b0.value == val
|
|
|
|
|
|
def test_binding_block() -> None:
|
|
m = tirx.Var("m", dtype="int64")
|
|
n = tirx.Var("n", dtype="int64")
|
|
shape = rx.const([16, 8], "int32")
|
|
b0 = rx.MatchCast(rx.Var("v0"), shape, R.Tensor([m, n], "int32"))
|
|
|
|
v0 = rx.Var("v0")
|
|
val = rx.const(np.random.rand(24, 56))
|
|
b1 = rx.VarBinding(v0, val)
|
|
|
|
block0 = rx.BindingBlock([b0, b1])
|
|
assert block0.bindings[0] == b0
|
|
assert block0.bindings[1] == b1
|
|
|
|
|
|
def test_dataflow_block() -> None:
|
|
m = tirx.Var("m", dtype="int64")
|
|
n = tirx.Var("n", dtype="int64")
|
|
shape = rx.const([16, 8], "int32")
|
|
b0 = rx.MatchCast(rx.Var("v0"), shape, R.Tensor([m, n], "int32"))
|
|
|
|
v0 = rx.Var("v0")
|
|
val = rx.const(np.random.rand(24, 56))
|
|
b1 = rx.VarBinding(v0, val)
|
|
|
|
block0 = rx.DataflowBlock([b0, b1])
|
|
assert block0.bindings[0] == b0
|
|
assert block0.bindings[1] == b1
|
|
assert isinstance(block0, rx.DataflowBlock)
|
|
|
|
|
|
def test_seq_expr() -> None:
|
|
x = rx.Var("foo")
|
|
bindings = [rx.VarBinding(x, rx.const(1))]
|
|
blocks = [rx.BindingBlock(bindings)]
|
|
seqe = rx.SeqExpr(blocks, x)
|
|
assert seqe.blocks[0] == blocks[0]
|
|
assert seqe.body == x
|
|
|
|
|
|
def test_func():
|
|
x = rx.Var("foo", R.Tensor(dtype="float32", ndim=2))
|
|
bindings = [rx.VarBinding(x, rx.const(1))]
|
|
blocks = [rx.BindingBlock(bindings)]
|
|
|
|
seqe = rx.SeqExpr(blocks, x)
|
|
ret_ty = R.Tensor(dtype="float32", ndim=-1)
|
|
func = rx.Function([x], seqe, ret_ty)
|
|
func = func.with_attr("global_symbol", "func")
|
|
assert func.params[0] == x
|
|
assert func.body == seqe
|
|
assert func.ret_ty == ret_ty
|
|
assert func.attrs["global_symbol"] == "func"
|
|
|
|
|
|
def test_shape_of():
|
|
shape = [96, 54]
|
|
v1 = rx.Var("v1", R.Tensor(shape))
|
|
s1 = rx.get_shape_of(v1)
|
|
for x, y in zip(shape, s1):
|
|
assert x == y
|
|
|
|
|
|
def test_shape_expr():
|
|
m = tirx.Var("m", dtype="int64")
|
|
n = tirx.Var("n", dtype="int64")
|
|
s = rx.ShapeExpr([m, n])
|
|
assert s.values[0] == m
|
|
assert s.values[1] == n
|
|
assert s[0] == m
|
|
assert s[1] == n
|
|
assert s[-1] == n
|
|
assert s[-2] == m
|
|
assert isinstance(s.ty, rx.ShapeType)
|
|
|
|
with pytest.raises(IndexError, match="ShapeExpr index out of range"):
|
|
s[2]
|
|
|
|
with pytest.raises(IndexError, match="ShapeExpr index out of range"):
|
|
s[-3]
|
|
|
|
shape_expr = rx.ShapeExpr([10, 20])
|
|
assert shape_expr.values[0] == 10
|
|
assert shape_expr.values[1] == 20
|
|
tvm.ir.assert_structural_equal(shape_expr.ty, R.Shape((10, 20)))
|
|
|
|
x = rx.Var("v0", R.Tensor((10, 20), "float32"))
|
|
assert x.ty.shape[0] == 10
|
|
assert x.ty.shape[1] == 20
|
|
tvm.ir.assert_structural_equal(x.ty.shape.ty, R.Shape((10, 20)))
|
|
|
|
m = tirx.Var("m", "int32")
|
|
with pytest.raises(RuntimeError, match="the value in ShapeType can only have dtype of int64"):
|
|
rx.ShapeExpr([m, 3])
|
|
|
|
|
|
def test_prim_value_helper_from_python_scalar():
|
|
int_value = rx.prim_value(1)
|
|
assert isinstance(int_value, tirx.IntImm)
|
|
tvm.ir.assert_structural_equal(int_value.ty, tvm.ir.PrimType("int64"))
|
|
assert int_value.value == 1
|
|
|
|
np_int_value = rx.prim_value(np.int32(2))
|
|
assert isinstance(np_int_value, tirx.IntImm)
|
|
tvm.ir.assert_structural_equal(np_int_value.ty, tvm.ir.PrimType("int64"))
|
|
assert np_int_value.value == 2
|
|
|
|
float_value = rx.prim_value(1.0)
|
|
assert isinstance(float_value, tirx.FloatImm)
|
|
tvm.ir.assert_structural_equal(float_value.ty, tvm.ir.PrimType("float64"))
|
|
assert float_value.value == 1.0
|
|
|
|
np_float_value = rx.prim_value(np.float32(1.5))
|
|
assert isinstance(np_float_value, tirx.FloatImm)
|
|
tvm.ir.assert_structural_equal(np_float_value.ty, tvm.ir.PrimType("float64"))
|
|
assert np_float_value.value == 1.5
|
|
|
|
|
|
def test_prim_value_helper_preserves_prim_expr():
|
|
float_imm = tirx.FloatImm("float32", 1.0)
|
|
assert rx.prim_value(float_imm).same_as(float_imm)
|
|
assert R.prim_value(float_imm).same_as(float_imm)
|
|
|
|
n = tirx.Var("n", "int64")
|
|
expr = n + 1
|
|
assert rx.prim_value(n).same_as(n)
|
|
assert rx.prim_value(expr).same_as(expr)
|
|
|
|
|
|
def test_prim_value_helper_rejects_relax_expr():
|
|
with pytest.raises(TypeError, match="Cannot convert"):
|
|
rx.prim_value(rx.Var("x"))
|
|
|
|
|
|
def test_string_imm():
|
|
s0 = rx.StringImm("hello")
|
|
s1 = rx.StringImm("hello")
|
|
assert s0.value == "hello"
|
|
_check_equal(s0, s1)
|
|
_check_json_roundtrip(s0)
|
|
|
|
|
|
def test_datatype_imm():
|
|
d0 = rx.DataTypeImm("int32")
|
|
d1 = rx.DataTypeImm("int32")
|
|
assert d0.value == "int32"
|
|
_check_equal(d0, d1)
|
|
_check_json_roundtrip(d0)
|
|
|
|
|
|
def test_call():
|
|
dtype = tvm.ir.PrimType("int32")
|
|
func = rx.Var("func", rx.FuncType([dtype], dtype))
|
|
arg = rx.Var("arg", dtype)
|
|
call = rx.Call(func, [arg])
|
|
assert call.op.same_as(func)
|
|
assert len(call.args) == 1
|
|
assert call.args[0].same_as(arg)
|
|
|
|
|
|
def test_call_accepts_core_expr_operator():
|
|
"""relax.Call aliases the core ir.Call constructor."""
|
|
dtype = tvm.ir.PrimType("int32")
|
|
func = rx.Var("func", dtype)
|
|
arg = rx.Var("arg", dtype)
|
|
|
|
call = rx.Call(func, [arg])
|
|
assert call.op.same_as(func)
|
|
assert len(call.args) == 1
|
|
assert call.args[0].same_as(arg)
|
|
_check_type_missing(call.ty)
|
|
|
|
|
|
def test_call_raises_error_for_missing_operator():
|
|
"""relax::Call requires a defined operator."""
|
|
with pytest.raises(ValueError, match="defined operator"):
|
|
rx.Call(None, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tvm.testing.main()
|
|
|
|
|
|
def test_make_shape_invalid_type():
|
|
with pytest.raises(TypeError):
|
|
make_shape(123)
|
|
|
|
|
|
def test_make_shape_valid_list():
|
|
shape = make_shape([1, 2, 3])
|
|
assert len(shape) == 3
|
|
|
|
|
|
def test_make_shape_valid_tuple():
|
|
shape = make_shape((4, 5))
|
|
assert len(shape) == 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tvm.testing.main()
|