| # 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 == "v0" |
| _check_type_missing(v0.ty) |
| shape = [54, 96] |
| v1 = rx.Var("v1", R.Tensor(shape, "float32")) |
| assert v1.name == "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_var_name_keyword_compatibility() -> None: |
| primary = tvm.ir.Var(name="primary") |
| legacy = tvm.ir.Var(name_hint="legacy") |
| assert primary.name == "primary" |
| assert legacy.name == "legacy" |
| assert not hasattr(primary, "name_hint") |
| assert not hasattr(legacy, "name_hint") |
| with pytest.raises(TypeError, match="Specify either name or name_hint, not both"): |
| tvm.ir.Var(name="primary", name_hint="legacy") |
| |
| |
| 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 == "v0" |
| _check_type_missing(v0.ty) |
| |
| shape = [54, 96] |
| v1 = rx.DataflowVar("v1", R.Tensor(shape, "float16")) |
| assert v1.name == "v1" |
| |
| assert isinstance(v1, rx.DataflowVar) |
| tvm.ir.assert_structural_equal(v1.ty, rx.TensorType(shape, "float16")) |
| |
| |
| def test_dataflow_var_name_keyword_compatibility() -> None: |
| primary = rx.DataflowVar(name="primary") |
| legacy = rx.DataflowVar(name_hint="legacy") |
| assert primary.name == "primary" |
| assert legacy.name == "legacy" |
| assert not hasattr(primary, "name_hint") |
| assert not hasattr(legacy, "name_hint") |
| with pytest.raises(TypeError, match="Specify either name or name_hint, not both"): |
| rx.DataflowVar(name="primary", name_hint="legacy") |
| |
| |
| 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", ty="int64") |
| n = tirx.Var("n", ty="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", ty="int64") |
| n = tirx.Var("n", ty="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 == "v0" |
| assert b0.value == val |
| |
| |
| def test_binding_block() -> None: |
| m = tirx.Var("m", ty="int64") |
| n = tirx.Var("n", ty="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", ty="int64") |
| n = tirx.Var("n", ty="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) |
| with_attr = func.with_attr("global_symbol", "func") |
| with_attrs = func.with_attr({"global_symbol": "func", "num_input": 1}) |
| |
| assert "global_symbol" not in func.attrs |
| assert with_attr.params[0] == x |
| assert with_attr.body == seqe |
| assert with_attr.ret_ty == ret_ty |
| assert with_attr.attrs["global_symbol"] == "func" |
| assert with_attrs.attrs["global_symbol"] == "func" |
| assert with_attrs.attrs["num_input"] == 1 |
| |
| |
| 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", ty="int64") |
| n = tirx.Var("n", ty="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() |