/** * \file src/opr/test/basic_arith/others.cpp * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") * * Copyright (c) 2014-2020 Megvii Inc. All rights reserved. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include "megbrain/test/helper.h" #include "megbrain/test/autocheck.h" #include "megbrain/test/host_static_calc.h" #include "megbrain/opr/basic_arith_wrapper.h" #include "megbrain/opr/blas.h" #include "megbrain/opr/io.h" #include "megbrain/opr/utility.h" #include "megbrain/opr/tensor_manip.h" #include "megbrain/utils/timer.h" #include "megdnn/tensor_iter.h" #include using namespace mgb; TEST(TestOprBasicArith, AddUpdate) { constexpr size_t SIZE = 123456; opr::AddUpdate::Param param{2, -1, 0.5f}; HostTensorGenerator<> gen; auto host_x = gen({SIZE}), host_y = gen({SIZE}); auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); SymbolVar dev_x_shared = opr::SharedDeviceTensor::make( *graph, dev_x, {"x"}), dev_y = opr::Host2DeviceCopy::make(*graph, host_y, {"y"}), dev_x_updated = opr::AddUpdate::make(dev_x_shared, dev_y, param); auto func = graph->compile({{ dev_x_updated, [&](DeviceTensorND &){}}}); func->execute(); ASSERT_EQ(dev_x->raw_ptr(), dev_x_updated.node()->prev_dev_ptr()); func->to_json()->writeto_fpath(output_file("add_update_graph.json")); HostTensorND get{CompNode::load("xpu0")}; get.copy_from(*dev_x).sync(); ASSERT_TRUE(get.layout().eq_layout(host_x->layout())); auto x = host_x->ptr(), y = host_y->ptr(), z = get.ptr(); for (size_t i = 0; i < SIZE; i ++) { auto expect = x[i] * param.alpha->get_cast() + y[i] * param.beta->get_cast() + param.bias->get_cast(); MGB_ASSERT_FLOAT_EQ(expect, z[i]); } } TEST(TestOprBasicArith, AddUpdateInt) { constexpr size_t SIZE = 123; opr::AddUpdate::Param param{2, -1, 3}; HostTensorGenerator gen; auto host_x = gen({SIZE}), host_y = gen({SIZE}); auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); SymbolVar dev_x_shared = opr::SharedDeviceTensor::make( *graph, dev_x, {"x"}), dev_y = opr::Host2DeviceCopy::make(*graph, host_y, {"y"}), dev_x_updated = opr::AddUpdate::make(dev_x_shared, dev_y, param); auto func = graph->compile({{ dev_x_updated, [&](DeviceTensorND &){}}}); func->execute(); ASSERT_EQ(dev_x->raw_ptr(), dev_x_updated.node()->prev_dev_ptr()); HostTensorND get{CompNode::load("xpu0")}; get.copy_from(*dev_x).sync(); ASSERT_TRUE(get.layout().eq_layout(host_x->layout())); auto x = host_x->ptr(), y = host_y->ptr(), z = get.ptr(); for (size_t i = 0; i < SIZE; i ++) { auto expect = x[i] * param.alpha->get_cast() + y[i] * param.beta->get_cast() + param.bias->get_cast(); ASSERT_EQ(expect, z[i]) << ssprintf("i=%zu x=%d y=%d", i, x[i], y[i]); } ASSERT_NO_THROW(func->execute()); param.bias->set(2.3f); ASSERT_THROW(func->execute(), MegDNNError); } TEST(TestOprBasicArith, DynAddUpdate) { constexpr size_t SIZE = 10; HostTensorGenerator<> gen; auto host_x = gen({SIZE}), host_y = gen({SIZE}); auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); auto x = opr::SharedDeviceTensor::make(*graph, dev_x, {"x"}), y = opr::MarkDynamicVar::make(opr::Host2DeviceCopy::make(*graph, host_y, {"y"})), x_updated = opr::AddUpdate::make(x, y, {}); ASSERT_FALSE(cg::is_static_var_shape(y.node())); ASSERT_TRUE(cg::is_static_var_shape(x_updated.node())); auto func = graph->compile({{x_updated, [&](DeviceTensorND &){}}}); func->execute(); HostTensorND host_xu; host_xu.copy_from(*dev_x).sync(); ASSERT_TRUE(host_xu.layout().eq_layout(host_x->layout())); { auto x = host_x->ptr(), y = host_y->ptr(), z = host_xu.ptr(); for (size_t i = 0; i < SIZE; i ++) { MGB_ASSERT_FLOAT_EQ(x[i] + y[i], z[i]); } } } TEST(TestOprBasicArith, AddUpdateBroadcast) { constexpr size_t SIZE = 123456; opr::AddUpdate::Param param{-1.2f, 2.1f, -4}; HostTensorGenerator<> gen; auto host_x = gen({SIZE}); auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); SymbolVar x = opr::SharedDeviceTensor::make(*graph, dev_x, {"x"}), delta = opr::Subtensor::make(x, {opr::Subtensor::AxisIndexer::make_index(0, x.make_scalar(3))}), x_updated = opr::AddUpdate::make(x, delta, param); auto func = graph->compile({{x_updated, {}}}); func->execute(); HostTensorND get{CompNode::load("xpu0")}; get.copy_from(*dev_x).sync(); ASSERT_TRUE(get.layout().eq_layout(host_x->layout())); auto xp = host_x->ptr(), z = get.ptr(); for (size_t i = 0; i < SIZE; ++ i) { auto expect = xp[i] * param.alpha->get_cast() + xp[3] * param.beta->get_cast() + param.bias->get_cast(); MGB_ASSERT_FLOAT_EQ(expect, z[i]); } } TEST(TestOprBasicArith, AddUpdateNan) { constexpr size_t SIZE = 23; HostTensorGenerator<> gen; auto host_x = gen({SIZE}), host_src = gen({1}); host_x->ptr()[0] = NAN; auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); SymbolVar x = opr::SharedDeviceTensor::make(*graph, dev_x, {"x"}), dest = opr::Host2DeviceCopy::make(*graph, host_src), xu = opr::AddUpdate::make(x, dest, {0.f, 1}); auto func = graph->compile({{xu, {}}}); func->execute(); HostTensorND host_y; host_y.copy_from(*dev_x).sync(); for (size_t i = 0; i < SIZE; ++ i) MGB_ASSERT_FLOAT_EQ(host_src->ptr()[0], host_y.ptr()[i]); } TEST(TestOprBasicArith, AddInplace) { constexpr size_t SIZE = 102400; HostTensorGenerator<> gen; auto host_opr0 = gen({SIZE}), host_opr1 = gen({SIZE}), host_opr2 = gen({SIZE}); // for operations with commutable input, must check both input order: // opr1 + opr0, opr1 + opr2 auto graph = ComputingGraph::make(); auto opr0 = opr::Host2DeviceCopy::make_no_fwd(*graph, host_opr0, {"opr0"}), opr1 = opr::Host2DeviceCopy::make_no_fwd(*graph, host_opr1, {"opr1"}), opr2 = opr::Host2DeviceCopy::make_no_fwd(*graph, host_opr2, {"opr2"}), sum0 = opr::add(opr1, opr0).rename("sum0"), sum1 = opr::add(opr1, opr2).rename("sum1"), sum2 = opr::add(opr2, opr0).rename("sum2"); // check dedup ASSERT_EQ(sum0.node(), (opr0 + opr1).node()); HostTensorND host_sum0, host_sum1; auto func = graph->compile({make_callback_copy(sum0, host_sum0), make_callback_copy(sum1, host_sum1)}); func->execute(); EXPECT_TRUE(dev_ptr(sum0) == dev_ptr(opr1) || dev_ptr(sum0) == dev_ptr(opr0)); EXPECT_TRUE(dev_ptr(sum1) == dev_ptr(opr1) || dev_ptr(sum1) == dev_ptr(opr2)); func->to_json()->writeto_fpath(output_file("TestAddInplaceFunc0.json")); ASSERT_TRUE(host_sum0.layout().eq_layout(host_opr0->layout())); ASSERT_TRUE(host_sum1.layout().eq_layout(host_opr0->layout())); auto o0 = host_opr0->ptr(), o1 = host_opr1->ptr(), o2 = host_opr2->ptr(), s0 = host_sum0.sync().ptr(), s1 = host_sum1.sync().ptr(); for (size_t i = 0; i < SIZE; i ++) { MGB_ASSERT_FLOAT_EQ(o1[i] + o0[i], s0[i]) << ssprintf("failed opr1(%.5f)+opr0(%.5f) at %zd", o1[i], o0[i], i); MGB_ASSERT_FLOAT_EQ(o1[i] + o2[i], s1[i]) << ssprintf("failed opr1(%.5f)+opr2(%.5f) at %zd", o1[i], o2[i], i); } *host_opr0 = *gen({SIZE}); *host_opr1 = *gen({SIZE}); *host_opr2 = *gen({SIZE}); HostTensorND host_sum2; func = graph->compile({make_callback_copy(sum0, host_sum0), make_callback_copy(sum1, host_sum1), make_callback_copy(sum2, host_sum2)}); func->execute(); func->to_json()->writeto_fpath(output_file("TestAddInplaceFunc1.json")); ASSERT_TRUE(host_sum0.layout().eq_layout(host_opr0->layout())); ASSERT_TRUE(host_sum1.layout().eq_layout(host_opr0->layout())); ASSERT_TRUE(host_sum2.layout().eq_layout(host_opr0->layout())); o0 = host_opr0->ptr(); o1 = host_opr1->ptr(); o2 = host_opr2->ptr(); s0 = host_sum0.ptr(); s1 = host_sum1.ptr(); auto s2 = host_sum2.sync().ptr(); for (size_t i = 0; i < SIZE; i ++) { MGB_ASSERT_FLOAT_EQ(o1[i] + o0[i], s0[i]) << ssprintf("failed opr1(%.5f)+opr0(%.5f) at %zd", o1[i], o0[i], i); MGB_ASSERT_FLOAT_EQ(o1[i] + o2[i], s1[i]) << ssprintf("failed opr1(%.5f)+opr2(%.5f) at %zd", o1[i], o2[i], i); MGB_ASSERT_FLOAT_EQ(o2[i] + o0[i], s2[i]) << ssprintf("failed opr2(%.5f)+opr0(%.5f) at %zd", o2[i], o0[i], i); } } TEST(TestOprBasicArith, AddUpdateOtherStream) { REQUIRE_GPU(1); constexpr size_t SIZE = 60; HostTensorGenerator<> gen; auto graph = ComputingGraph::make(); std::atomic_bool flag{false}; auto set_flag = [&flag](DeviceTensorND&) { flag = true; }; auto wait_flag = [&flag](DeviceTensorND&) { while (!flag) { using namespace std::literals; std::this_thread::sleep_for(0.2s); } }; std::shared_ptr host_val = gen({SIZE}); auto cn1 = CompNode::load("gpu0:0").change_stream(1); auto param = opr::SharedDeviceTensor::make(*graph, *host_val); param.node()->owner_opr()->node_prop().attribute().priority = std::numeric_limits::max(); auto copy = opr::Copy::make(param, cn1); auto add = (copy + 3) * 5; auto add_update = opr::AddUpdate::make(param, add, {}, {cn1}); auto callback = opr::CallbackInjector::make(add_update, set_flag); auto waiter = opr::CallbackInjector::make( opr::SharedDeviceTensor::make(*graph, *host_val), wait_flag); HostTensorND host_out0; HostTensorND host_out1; auto func = graph->compile({make_callback_copy(callback, host_out0), make_callback_copy(waiter, host_out1)}); func->execute(); } TEST(TestOprBasicArith, DisableAddUpdate) { constexpr size_t SIZE = 10; opr::AddUpdate::Param param{2, -1, 0.5f, 1}; HostTensorGenerator<> gen; auto host_x = gen({SIZE}), host_y = gen({SIZE}); auto dev_x = std::make_shared(CompNode::load("xpu0")); dev_x->copy_from(*host_x); auto graph = ComputingGraph::make(); SymbolVar dev_x_shared = opr::SharedDeviceTensor::make( *graph, dev_x, {"x"}), dev_y = opr::Host2DeviceCopy::make(*graph, host_y, {"y"}), dev_x_updated = opr::AddUpdate::make(dev_x_shared, dev_y, param); auto func = graph->compile({{ dev_x_updated, [&](DeviceTensorND &){}}}); func->execute(); ASSERT_EQ(dev_x->raw_ptr(), dev_x_updated.node()->prev_dev_ptr()); func->to_json()->writeto_fpath(output_file("add_update_graph.json")); HostTensorND get{CompNode::load("xpu0")}; get.copy_from(*dev_x).sync(); ASSERT_TRUE(get.layout().eq_layout(host_x->layout())); auto x = host_x->ptr(), y = get.ptr(); for (size_t i = 0; i < SIZE; i ++) { MGB_ASSERT_FLOAT_EQ(x[i], y[i]); } } TEST(TestOprBasicArith, AddUpdateVolatile) { constexpr int SIZE = 12222; opr::AddUpdate::Param param{2, -1, 0.5f}; HostTensorGenerator<> gen; auto cn = CompNode::load("xpu0"); for (auto dynamic_alloc : {false, true}) { // test on both static and dynamic allocation auto host_x = gen({SIZE << 1}), host_y = gen({SIZE << 1}); auto dev_x = std::make_shared(cn); DeviceTensorND dev_x0, dev_x1; HostTensorND host_sub; dev_x0.copy_from(*host_x).sync(); dev_x1.copy_from(*host_x).sync(); *dev_x = dev_x0; auto graph = ComputingGraph::make(); graph->options().force_dynamic_alloc = dynamic_alloc; SymbolVar dev_x_shared = opr::VolatileSharedDeviceTensor::make( *graph, dev_x, {"x"}), dev_y = opr::Host2DeviceCopy::make(*graph, host_y, {"y"}), dev_x_updated = opr::AddUpdate::make(dev_x_shared, dev_y, param), // check read-only forward on force updated var dev_x_updated_sub = opr::Subtensor::make(dev_x_updated, { opr::Subtensor::AxisIndexer::make_interval(-1, None, None, dev_x_shared.make_scalar(SIZE >> 1))}); auto func = graph->compile({ {dev_x_updated, [&](DeviceTensorND &){}}, {make_callback_copy(dev_x_updated_sub, host_sub)}}); auto run = [&] { HostTensorND origin_x{cn}, get{cn}; origin_x.copy_from(*dev_x).sync(); func->execute().wait(); ASSERT_EQ(dev_x->raw_ptr(), dev_x_updated.node()->prev_dev_ptr()); ASSERT_EQ(dev_x->raw_ptr(), dev_x_updated_sub.node()->prev_dev_ptr()); get.copy_from(*dev_x).sync(); ASSERT_TRUE(get.layout().eq_layout(origin_x.layout())); mgb_assert(origin_x.layout().is_contiguous() && get.layout().is_contiguous() && host_y->layout().is_contiguous()); auto x = origin_x.ptr(), y = host_y->ptr(), z = get.ptr(); bool bcast = dev_x->shape().ndim > 1; auto expect = [&](size_t i) { return x[i] * param.alpha->get_cast() + (bcast ? y[i / SIZE] : y[i]) * param.beta->get_cast() + param.bias->get_cast(); }; for (size_t i = 0; i < SIZE * 2; i ++) { MGB_ASSERT_FLOAT_EQ(expect(i), z[i]); } mgb_assert(host_sub.shape().total_nr_elems() == 4 && host_sub.layout().is_contiguous()); for (size_t i = 0; i < 4; ++ i) { size_t idx = i * (SIZE >> 1); MGB_ASSERT_FLOAT_EQ(expect(idx), host_sub.ptr()[i]); } }; run(); run(); *dev_x = dev_x1; // ptr change run(); host_x = gen({2, SIZE}); host_y->copy_from(*gen({2, 1})).sync(); dev_x->copy_from(*host_x).sync(); // shape change run(); } } // AddUpdate in gradient path but no gradient flows through it TEST(TestOprBasicArith, AddUpdateInGradPath) { auto graph = ComputingGraph::make(); HostTensorGenerator<> gen; auto dest = opr::SharedDeviceTensor::make(*graph, *gen({42})); auto host_x = gen({42}); auto x = opr::Host2DeviceCopy::make(*graph, host_x); // delta depends on x, but not differentiable wrt x // a invalid grad is registered for AddUpdate to fix this case auto delta = opr::VirtualDep::make({opr::SetGrad::make(x, nullptr), x}); auto updated = opr::AddUpdate::make(dest, delta); auto y = opr::reduce_ax_sum(updated + x, 0); auto dx = cg::grad(y, x); HostTensorND host_dx; auto func = graph->compile({make_callback_copy(dx, host_dx)}); func->execute(); for (size_t i = 0; i < host_dx.shape(0); ++i) { MGB_ASSERT_FLOAT_EQ(host_dx.ptr()[i], 1.f); } } TEST(TestOprBasicArith, MemFwd) { constexpr size_t SIZE = 12321; HostTensorGenerator<> gen; auto host_x = gen({SIZE}); auto graph = ComputingGraph::make(); auto x = opr::Host2DeviceCopy::make_no_fwd(*graph, host_x).rename("x"), y = opr::sin(x), z = y + 1; HostTensorND host_z; auto func = graph->compile({make_callback_copy(z, host_z)}); func->execute(); ASSERT_EQ(dev_ptr(x), dev_ptr(y)); ASSERT_EQ(dev_ptr(x), dev_ptr(z)); for (size_t i = 0; i < SIZE; ++ i) { MGB_ASSERT_FLOAT_EQ(host_z.ptr()[i], std::sin(host_x->ptr()[i]) + 1.f); }; } TEST(TestOprBasicArith, BinaryGradWithBroadcast) { using Checker = AutoOprChecker<3, 1>; auto make_graph = [](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {inputs[0] + (opr::MarkDynamicVar::make(inputs[1]) + inputs[2])}; }; auto fwd = [](Checker::NumOutArray &dest, Checker::NumInpArray inp) { host_add(dest[0], *inp[0], *inp[1]); host_add(dest[0], dest[0], *inp[2]); }; Checker(make_graph, fwd). run({TensorShape{2, 3}, TensorShape{2, 3}, TensorShape{1}}). run({TensorShape{1, 5}, TensorShape{1, 1}, TensorShape{5, 1}}). run({TensorShape{2, 1, 1}, TensorShape{1, 3, 1}, TensorShape{1, 1, 4}}). run({TensorShape{1, 1, 1}, TensorShape{1, 3, 1}, TensorShape{2, 3, 4}}); } TEST(TestOprBasicArith, BinaryBroadcastCorrectness) { using Checker = AutoOprChecker<2, 1>; auto run = [&](bool dyn_inp) { auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { auto x = inputs[0], y = inputs[1]; if (dyn_inp) { x = opr::MarkDynamicVar::make(x); y = opr::MarkDynamicVar::make(y); } x.rename("x"); y.rename("y"); return {x * y}; }; auto fwd = [](Checker::NumOutArray &dest, Checker::NumInpArray inp) { TensorShape oshp; megdnn::Elemwise::deduce_shape({inp[0]->shape(), inp[1]->shape()}, oshp); auto &&dv = dest[0].comp_node(inp[0]->comp_node()).resize(oshp); auto &&iv0 = inp[0]->sub(SubTensorSpec::make_from_layout( inp[0]->layout().broadcast(oshp))), &&iv1 = inp[1]->sub(SubTensorSpec::make_from_layout( inp[1]->layout().broadcast(oshp))); auto it0 = megdnn::tensor_iter_valonly( iv0.as_megdnn()).begin(), it1 = megdnn::tensor_iter_valonly( iv1.as_megdnn()).begin(); for (size_t i = 0, it = oshp.total_nr_elems(); i < it; ++ i) { dv.ptr()[i] = *it0 * *it1; ++ it0; ++ it1; } }; Checker::RunOptions opt; opt.numdiff_eps = 1; Checker(make_graph, fwd). run({TensorShape{5, 3}, {5, 3}}, opt). run({TensorShape{2, 2, 1, 1}, {1, 2, 1, 1}}, opt). run({TensorShape{1, 2}, {2, 1}}, opt). run({TensorShape{3, 2, 5}, {1}}, opt). run({TensorShape{4, 5, 1, 1}, {4, 5, 6, 7}}, opt). run({TensorShape{8, 4, 1, 1}, {1, 4, 5, 1}}, opt); }; run(false); run(true); } TEST(TestOprBasicArith, Optimize) { auto graph = ComputingGraph::make(); HostTensorGenerator<> gen; auto host_x = gen({23}); auto x = opr::Host2DeviceCopy::make(*graph, host_x), x_sum2 = opr::reduce_sum( opr::pow(x, x.make_scalar(2)), x.make_scalar(1)); ASSERT_EQ(opr::Reduce::Mode::SUM_SQR, x_sum2.node()->owner_opr()->cast_final_safe(). param().mode); float sum2 = 0; auto xptr = host_x->ptr(); for (size_t i = 0, it = host_x->shape().total_nr_elems(); i < it; ++ i) { sum2 += xptr[i] * xptr[i]; } HostTensorND host_x_sum2; auto func = graph->compile({make_callback_copy(x_sum2, host_x_sum2)}); func->execute(); ASSERT_EQ(TensorShape{1}, host_x_sum2.shape()); MGB_ASSERT_FLOAT_EQ(sum2, host_x_sum2.ptr()[0]); } TEST(TestOprBasicArith, TypeCvt) { auto graph = ComputingGraph::make(); HostTensorGenerator<> gen{0, 1000}; auto host_x = gen({23}); auto x = opr::Host2DeviceCopy::make(*graph, host_x), y = opr::TypeCvt::make(x, dtype::Int32{}); HostTensorND host_y; auto func = graph->compile({make_callback_copy(y, host_y)}); func->execute(); auto px = host_x->ptr(); auto py = host_y.ptr(); for (size_t i = 0; i < 23; ++i) { ASSERT_EQ(static_cast(px[i]), py[i]); } host_x->resize({3, 0}); func->execute(); ASSERT_EQ(TensorShape({3, 0}), host_y.shape()); } TEST(TestOprBasicArith, TypeCvtBool) { auto graph = ComputingGraph::make(); HostTensorGenerator gen; auto host_x = gen({3}); auto px = host_x->ptr(); px[0] = -1; px[1] = 0; px[2] = 1; auto x = opr::Host2DeviceCopy::make(*graph, host_x), y = opr::TypeCvt::make(x, dtype::Bool{}); HostTensorND host_y; auto func = graph->compile({make_callback_copy(y, host_y)}); func->execute(); auto py = host_y.ptr(); for (size_t i = 0;i < 3;i ++) { ASSERT_EQ(static_cast(px[i]), py[i]); } ASSERT_EQ(TensorShape({3}), host_y.shape()); } TEST(TestOprBasicArith, TypeCvtFromBool) { auto graph = ComputingGraph::make(); HostTensorGenerator gen; auto host_x = gen({2}); auto px = host_x->ptr(); px[0] = true; px[1] = false; auto x = opr::Host2DeviceCopy::make(*graph, host_x), y = opr::TypeCvt::make(x, dtype::Int32{}); HostTensorND host_y; auto func = graph->compile({make_callback_copy(y, host_y)}); func->execute(); auto py = host_y.ptr(); for (size_t i = 0;i < 2;i ++) { ASSERT_EQ(static_cast(px[i]), py[i]); } ASSERT_EQ(TensorShape({2}), host_y.shape()); } TEST(TestOprBasicArith, ElemwiseMemFwd) { auto graph = ComputingGraph::make(); graph->options().graph_opt_level = 0; HostTensorGenerator<> gen; auto host_x = gen({3, 3}), host_y = gen({3, 3}); // x[:, ::-1] auto rev = [](SymbolVar x) { return opr::Subtensor::make(x, {opr::Subtensor::AxisIndexer::make_interval( 1, None, None, x.make_scalar(-1))}); }; auto x = opr::Host2DeviceCopy::make_no_fwd(*graph, host_x), y = opr::Host2DeviceCopy::make_no_fwd(*graph, host_y), y0 = rev(y), y1 = rev(x), z0 = x + y0, z1 = x + y1, z2 = x + x; auto check = [&graph, &host_x, x](SymbolVar y, SymbolVar z, float* py, bool rev_y, bool should_fwd) { HostTensorND host_z; auto func = graph->compile({make_callback_copy(z, host_z)}); func->execute(); HostTensorND expect; expect.copy_from(*host_x); auto pe = expect.ptr(); for (size_t i = 0; i < 3; ++i) { auto cur_py = py + i * 3 + static_cast(rev_y) * 2; for (size_t j = 0; j < 3; ++j) { pe[i * 3 + j] += *cur_py; cur_py += rev_y ? -1 : 1; } } MGB_ASSERT_TENSOR_EQ(expect, host_z); auto xptr = dev_ptr(x), yptr = dev_ptr(y), zptr = dev_ptr(z); if (should_fwd) { ASSERT_EQ(zptr, xptr); } else { ASSERT_NE(zptr, xptr); ASSERT_NE(zptr, yptr); } }; check(y0, z0, host_y->ptr(), true, true); ASSERT_EQ(dev_ptr(y) + 2 * sizeof(float), dev_ptr(y0)); check(y1, z1, host_x->ptr(), true, false); ASSERT_EQ(dev_ptr(x) + 2 * sizeof(float), dev_ptr(y1)); check(x, z2, host_x->ptr(), false, true); } TEST(TestOprBasicArith, ElemwiseRequireContig) { auto graph = ComputingGraph::make(); graph->options().graph_opt_level = 0; HostTensorGenerator<> gen; auto host_x = gen({3, 3}), host_y = gen({1, 3}); auto x = opr::Host2DeviceCopy::make_no_fwd(*graph, host_x), y = opr::Host2DeviceCopy::make_no_fwd(*graph, host_y), xt = opr::Dimshuffle::make(x, {1, 0}), yb = y.broadcast({3, 3}), z = xt + yb; HostTensorND host_z; auto func = graph->compile({make_callback_copy(z, host_z)}); func->execute(); HostTensorND expect{host_x->comp_node(), host_x->dtype()}; expect.resize({3, 3}); auto px = host_x->ptr(), py = host_y->ptr(), pe = expect.ptr(); for (size_t i = 0; i < 3; ++i) { for (size_t j = 0; j < 3; ++j) { pe[i * 3 + j] = px[j * 3 + i] + py[j]; } } MGB_ASSERT_TENSOR_EQ(expect, host_z); ASSERT_NE(dev_ptr(x), dev_ptr(xt)); ASSERT_EQ(dev_ptr(y), dev_ptr(yb)); ASSERT_EQ(dev_ptr(xt), dev_ptr(z)); } TEST(TestOprBasicArith, TypeCvtDedup) { HostTensorGenerator<> gen; auto host_x = gen({5, 5, 5, 5}); auto graph = ComputingGraph::make(); auto x = opr::Host2DeviceCopy::make(*graph, host_x); dtype::Quantized8Asymm dtype1(0.01f, (uint8_t) 123); dtype::Quantized8Asymm dtype2(0.02f, (uint8_t) 234); auto cvt1 = opr::TypeCvt::make(x, dtype1); auto cvt2 = opr::TypeCvt::make(x, dtype2); ASSERT_NE(cvt1.node(), cvt2.node()); dtype::Quantized8Asymm dtype3(0.01f, (uint8_t) 123); auto cvt3 = opr::TypeCvt::make(x, dtype3); ASSERT_EQ(cvt1.node(), cvt3.node()); } TEST(TestOprBasicArith, PowC) { using Checker = AutoOprChecker<1, 1>; SymbolVar inp, sub; auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray { // test non-contig inp = inputs[0]; sub = opr::Subtensor::make( inp, {opr::Subtensor::AxisIndexer::make_interval( 1, None, inputs[0].make_scalar(-2), None)}); return {opr::PowC::make(sub, 2.f)}; }; auto fwd = [](Checker::NumOutArray& dest, Checker::NumInpArray inp) { TensorShape oshp = inp[0]->shape(); oshp[1] -= 2; size_t size_x = oshp[0], strd_x = inp[0]->shape().total_nr_elems() / size_x, size_y = oshp.total_nr_elems() / size_x; auto px = inp[0]->ptr(), py = dest[0].resize(oshp).ptr(); for (size_t i = 0; i < size_x; ++i) { for (size_t j = 0; j < size_y; ++j) { float xv = px[i * strd_x + j], yv = xv * xv; py[i * size_y + j] = yv; } } }; Checker checker{make_graph, fwd}; checker.run({TensorShape{2, 3}}) .run({TensorShape{12, 33}}) .run({TensorShape{5, 33, 7}}); ASSERT_EQ(prev_dev_ptr(inp), prev_dev_ptr(sub)); } TEST(TestOprBasicArith, PowCInfer) { HostTensorGenerator<> gen; auto run = [&](bool contig) { auto host_x = gen({3, contig ? 4u : 5u}); auto graph = ComputingGraph::make(); auto x = opr::Host2DeviceCopy::make_no_fwd(*graph, host_x), xsub = opr::Subtensor::make( x, {opr::Subtensor::AxisIndexer::make_interval( 1, None, x.make_scalar(4), None)}), y = opr::PowC::make(xsub, 4.f); auto y_infer = graph->static_infer_manager().infer_value(y.node()); HostTensorND host_y; auto func = graph->compile({make_callback_copy(y, host_y)}); func->execute(); MGB_ASSERT_TENSOR_EQ(host_y, HostTensorND::make_proxy(y_infer)); ASSERT_EQ(prev_dev_ptr(x), prev_dev_ptr(xsub)); if (contig) { // inplace computing ASSERT_EQ(prev_dev_ptr(xsub), prev_dev_ptr(y)); } else { ASSERT_NE(prev_dev_ptr(xsub), prev_dev_ptr(y)); } }; run(false); run(true); } // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}