# 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. import collections import numpy as np import pytest import tvm from tvm import te from tvm import relay from tvm.relay import GlobalVar from tvm.relay.analysis import free_vars, free_type_vars from tvm.relay import create_executor, transform from tvm.relay.transform import gradient from tvm.relay.prelude import Prelude from tvm.relay.testing import ( make_nat_expr, run_infer_type, check_grad, rand, count_ops, ) import tvm.relay.op as op def test_fo_id(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], x) func = run_infer_type(func) back_func = run_infer_type(gradient(func, mode="first_order")) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), x.numpy()) tvm.testing.assert_allclose(grad.numpy(), np.ones_like(x.numpy())) def test_id(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], x) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), x.numpy()) tvm.testing.assert_allclose(grad.numpy(), np.ones_like(x.numpy())) def test_relu(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], op.nn.relu(x)) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) # gradient will implicitly check that no graph appear in result def test_add(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], x + x) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), 2 * x.numpy()) tvm.testing.assert_allclose(grad.numpy(), 2 * np.ones_like(x.numpy())) def test_check_grad(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) y = relay.var("y", t) func = relay.Function([x, y], x + y) check_grad(func) def test_temp_add(): scope = relay.ScopeBuilder() shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) y = scope.let("y", x + x) scope.ret(y + y) func = relay.Function([x], scope.get()) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), 4 * x.numpy()) tvm.testing.assert_allclose(grad.numpy(), 4 * np.ones_like(x.numpy())) def test_sub(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], x - x) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), np.zeros_like(x.numpy())) tvm.testing.assert_allclose(grad.numpy(), np.zeros_like(x.numpy())) def test_broadcast_add(): shape1 = (3, 4, 1) shape2 = (1, 5) dtype = "float32" x_nd = rand(dtype, *shape1) y_nd = rand(dtype, *shape2) x_np = x_nd.numpy() y_np = y_nd.numpy() expected_forward = x_np + y_np t1 = relay.TensorType(shape1, dtype) t2 = relay.TensorType(shape2, dtype) x = relay.var("x", t1) y = relay.var("y", t2) func = relay.Function([x, y], x + y) func = run_infer_type(func) full_func = run_infer_type(gradient(func)) assert full_func.checked_type == relay.FuncType( [t1, t2], relay.TupleType( [relay.TensorType(expected_forward.shape, dtype), relay.TupleType([t1, t2])] ), ) forward, (grad_x, grad_y) = create_executor().evaluate(full_func)(x_nd, y_nd) tvm.testing.assert_allclose(forward.numpy(), expected_forward) tvm.testing.assert_allclose( grad_x.numpy(), np.ones_like(expected_forward).sum(axis=2, keepdims=True) ) tvm.testing.assert_allclose( grad_y.numpy(), np.ones_like(expected_forward).sum(axis=(0, 1), keepdims=True).squeeze(axis=0), ) def test_broadcast_subtract(): shape1 = (3, 4, 1) shape2 = (1, 5) dtype = "float32" x_nd = rand(dtype, *shape1) y_nd = rand(dtype, *shape2) x_np = x_nd.numpy() y_np = y_nd.numpy() expected_forward = x_np - y_np t1 = relay.TensorType(shape1, dtype) t2 = relay.TensorType(shape2, dtype) x = relay.var("x", t1) y = relay.var("y", t2) func = relay.Function([x, y], x - y) func = run_infer_type(func) full_func = run_infer_type(gradient(func)) assert full_func.checked_type == relay.FuncType( [t1, t2], relay.TupleType( [relay.TensorType(expected_forward.shape, dtype), relay.TupleType([t1, t2])] ), ) forward, (grad_x, grad_y) = create_executor().evaluate(full_func)(x_nd, y_nd) tvm.testing.assert_allclose(forward.numpy(), expected_forward) tvm.testing.assert_allclose( grad_x.numpy(), np.ones_like(expected_forward).sum(axis=2, keepdims=True) ) tvm.testing.assert_allclose( grad_y.numpy(), -np.ones_like(expected_forward).sum(axis=(0, 1), keepdims=True).squeeze(axis=0), ) def _test_tuple(mode): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) y = relay.var("y", t) z = relay.var("z", t) if mode == "higher_order": tup = relay.Var("tup") func = relay.Function( [x, y, z], relay.Let( tup, relay.Tuple([x, y, z]), relay.TupleGetItem(tup, 0) + relay.TupleGetItem(tup, 1) - relay.TupleGetItem(tup, 2), ), ) else: # first order does not do let. tup = relay.Tuple([x, y, z]) func = relay.Function( [x, y, z], relay.TupleGetItem(tup, 0) + relay.TupleGetItem(tup, 1) - relay.TupleGetItem(tup, 2), ) func = run_infer_type(func) back_func = run_infer_type(gradient(func, mode=mode)) assert back_func.checked_type == relay.FuncType( [t, t, t], relay.TupleType([t, relay.TupleType([t, t, t])]) ) x_nd = rand(dtype, *shape) y_nd = rand(dtype, *shape) z_nd = rand(dtype, *shape) x_np = x_nd.numpy() y_np = y_nd.numpy() z_np = z_nd.numpy() expected_forward = x_np + y_np - z_np forward, (grad_x, grad_y, grad_z) = create_executor().evaluate(back_func)(x_nd, y_nd, z_nd) tvm.testing.assert_allclose(forward.numpy(), expected_forward) tvm.testing.assert_allclose(grad_x.numpy(), np.ones_like(grad_x.numpy())) tvm.testing.assert_allclose(grad_y.numpy(), np.ones_like(grad_y.numpy())) tvm.testing.assert_allclose(grad_z.numpy(), -1 * np.ones_like(grad_z.numpy())) def _test_tuple_argument(mode): shape = (2, 3) dtype = "float32" tensor_type = relay.TensorType(shape, dtype) fields = 3 tuple_type = relay.TupleType([tensor_type] * fields) tup = relay.var("tup", type_annotation=tuple_type) body = relay.TupleGetItem(tup, 0) for i in range(1, fields): body = relay.add(body, relay.TupleGetItem(tup, i)) func = relay.Function([tup], body) func = run_infer_type(func) back_func = run_infer_type(gradient(func, mode=mode)) xs = [rand(dtype, *shape) for _ in range(fields)] xs_np = np.array([x.numpy() for x in xs]) expected_forward = np.sum(xs_np, axis=0) forward, grad = create_executor().evaluate(back_func)(tuple(xs)) tvm.testing.assert_allclose(forward.numpy(), expected_forward) for field in grad[0]: tvm.testing.assert_allclose(field.numpy(), np.ones_like(field.numpy())) def test_tuple(): _test_tuple("higher_order") def test_tuple_first_order(): _test_tuple("first_order") @pytest.mark.xfail(raises=tvm.error.TVMError) def test_tuple_argument(): # fails until we add support for top-level tuple arguments in higher-order AD _test_tuple_argument("higher_order") def test_tuple_argument_first_order(): _test_tuple_argument("first_order") def test_pow(): mod = tvm.IRModule() p = Prelude(mod) p.mod.import_from_std("nat.rly") nat_iterate = mod.get_global_var("nat_iterate") shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) double = relay.Function([x], x + x) i = relay.var("i", t) func = relay.Function([i], nat_iterate(double, make_nat_expr(p, 3))(i)) mod["main"] = func mod = transform.InferType()(mod) mod["main"] = gradient(mod["main"], mod=mod) m = transform.InferType()(mod) back_func = m["main"] assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) i_nd = rand(dtype, *shape) forward, (grad_i,) = create_executor(mod=mod).evaluate(back_func)(i_nd) tvm.testing.assert_allclose(forward.numpy(), 8 * i_nd.numpy()) tvm.testing.assert_allclose(grad_i.numpy(), 8 * np.ones_like(grad_i.numpy())) def test_ref(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) r = relay.Var("r") u = relay.Var("u") body = relay.RefRead(r) body = relay.Let(u, relay.RefWrite(r, relay.RefRead(r) + relay.RefRead(r)), body) body = relay.Let(r, relay.RefCreate(x), body) func = relay.Function([x], body) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x_nd = rand(dtype, *shape) forward, (grad_x,) = create_executor().evaluate(back_func)(x_nd) tvm.testing.assert_allclose(forward.numpy(), 2 * x_nd.numpy()) tvm.testing.assert_allclose(grad_x.numpy(), 2 * np.ones_like(grad_x.numpy())) def test_square_second_order(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) func = relay.Function([x], x * x) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) y = relay.var("y", t) back_func_adjusted = relay.Function( [y], relay.TupleGetItem(relay.TupleGetItem(back_func(y), 1), 0) ) back_func_adjusted = run_infer_type(back_func_adjusted) back_back_func = run_infer_type(gradient(back_func_adjusted)) assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x_nd = rand(dtype, *shape) forward, (grad_x,) = create_executor().evaluate(back_back_func)(x_nd) tvm.testing.assert_allclose(forward.numpy(), 2 * x_nd.numpy()) tvm.testing.assert_allclose(grad_x.numpy(), 2 * np.ones_like(grad_x.numpy())) def test_if(): x = relay.var("x", shape=(1, 16, 64, 64)) y = relay.var("y", shape=(1, 16, 64, 64)) cond = relay.var("cond", shape=(), dtype="uint1") net = relay.If(cond, x, y) net = relay.log(net) func = relay.Function(free_vars(net), net) func = run_infer_type(func) net = gradient(func, mode="higher_order") net = run_infer_type(net) def test_grad_tuple(): scope = relay.ScopeBuilder() shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.var("x", t) y = scope.let("y", x + x) scope.ret(relay.Tuple([y + y, y])) func = relay.Function([x], scope.get()) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) assert back_func.checked_type == relay.FuncType( [t], relay.TupleType([relay.TupleType([t, t]), relay.TupleType([t])]) ) x = rand(dtype, *shape) (forward_four, forward_two), (grad,) = create_executor().evaluate(back_func)(x) tvm.testing.assert_allclose(forward_four.numpy(), 4 * x.numpy()) tvm.testing.assert_allclose(forward_two.numpy(), 2 * x.numpy()) tvm.testing.assert_allclose(grad.numpy(), 4 * np.ones_like(x.numpy())) def test_concat(): shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) rt = relay.TensorType((10, 20), dtype) x = relay.var("x", t) y = op.concatenate([x, x], axis=1) func = relay.Function([x], y) func = run_infer_type(func) back_func = run_infer_type(gradient(func)) tvm.ir.assert_structural_equal( back_func.checked_type, relay.FuncType([t], relay.TupleType([rt, relay.TupleType([t])])) ) # no value validation as concatenate has dummy gradient right now. def test_no_duplication(): x = tvm.relay.Var("x", type_annotation=tvm.relay.TensorType([12, 12])) y = tvm.relay.Var("y", type_annotation=tvm.relay.TensorType([12, 12])) xy = tvm.relay.nn.dense(x, y) m = tvm.relay.sum(xy, keepdims=True) s = tvm.relay.sum(xy - m) fn = tvm.relay.Function([x, y], s) fn = run_infer_type(fn) gr = tvm.relay.transform.gradient(fn, mode="first_order") counts = count_ops(gr) assert counts["nn.dense"] == 3, "We expect 3 dense (1 forward, two backward)" def test_no_duplication_tuples(): x = tvm.relay.Var("x", type_annotation=tvm.relay.TensorType([12, 12])) y = tvm.relay.Var("y", type_annotation=tvm.relay.TensorType([12, 12])) xy = tvm.relay.nn.dense(x, y) t = relay.Tuple([xy, xy]) m = tvm.relay.sum(xy, keepdims=True) s = tvm.relay.sum(relay.TupleGetItem(t, 0) - m) fn = tvm.relay.Function([x, y], s) fn = run_infer_type(fn) gr = tvm.relay.transform.gradient(fn, mode="first_order") counts = count_ops(gr) assert counts["nn.dense"] == 3, "We expect 3 dense (1 forward, two backward)" def test_global_function(): m = tvm.IRModule() shape = (10, 10) dtype = "float32" t = relay.TensorType(shape, dtype) x = relay.Var("x", t) d = GlobalVar("double") m[d] = relay.Function([x], x + x) y = relay.Var("y", t) q = GlobalVar("q") m[q] = relay.Function([y], d(d(y))) g = GlobalVar("grad") m = tvm.relay.transform.InferType()(m) m[g] = tvm.relay.transform.gradient(q, m) m = tvm.relay.transform.InferType()(m) back_func = m[g] assert back_func.checked_type == relay.FuncType([t], relay.TupleType([t, relay.TupleType([t])])) x = rand(dtype, *shape) forward, (grad,) = create_executor(mod=m).evaluate(back_func)(x) tvm.testing.assert_allclose(forward.numpy(), 4 * x.numpy()) tvm.testing.assert_allclose(grad.numpy(), 4 * np.ones_like(x.numpy())) if __name__ == "__main__": pytest.main([__file__])