GitOrigin-RevId: db4eba5877
tags/v0.5.0
| @@ -226,17 +226,19 @@ void mixin::IndexingMultiAxisVecMegDNNOprHolder<Opr>::record_megdnn_opr( | |||
| } | |||
| /* ==================== MultiAxisVecFancyIndexingHelper ==================== */ | |||
| const megdnn::IndexingMultiAxisVec::IndexDesc& | |||
| std::pair<const megdnn::IndexingMultiAxisVec::IndexDesc&, bool> | |||
| intl::MultiAxisVecFancyIndexingHelper::make_megdnn_index_desc( | |||
| size_t inp_ndim, bool warn_all_scalar) { | |||
| auto &&index = m_megdnn_index_cache; | |||
| index.clear(); | |||
| bool is_empty_shape = false; | |||
| for (auto i: reverse_adaptor(m_input2idxonly_axis_indexer)) { | |||
| if (i) { | |||
| index.push_back({ | |||
| i->axis.get(inp_ndim), | |||
| i->idx.node()->dev_tensor().as_megdnn()}); | |||
| is_empty_shape |= index.back().vec.layout.is_empty(); | |||
| } | |||
| } | |||
| @@ -264,7 +266,7 @@ intl::MultiAxisVecFancyIndexingHelper::make_megdnn_index_desc( | |||
| m_scalar_idx_warn_printed = true; | |||
| } | |||
| return index; | |||
| return {index, is_empty_shape}; | |||
| } | |||
| /* ==================== IndexingMultiAxisVecBase ==================== */ | |||
| @@ -272,6 +274,8 @@ template<class Opr> | |||
| cg::OperatorNodeBase::NodeProp* | |||
| IndexingMultiAxisVecBase<Opr>::do_make_node_prop() const { | |||
| auto prop = Super::do_make_node_prop(); | |||
| // TODO: should also allow input shape is empty if any | |||
| // indexer's shape is empty | |||
| for (auto i: m_input2idxonly_axis_indexer) { | |||
| if (i) { | |||
| prop->add_dep_type_existing_var( | |||
| @@ -360,13 +364,13 @@ void IndexingMultiAxisVecBase<Opr>::scn_do_execute() { | |||
| auto &&index_desc = make_megdnn_index_desc( | |||
| inp.layout().ndim, ShouldWarnOnScalarIndexer<Opr>::val); | |||
| auto &&odev = output(0)->dev_tensor(); | |||
| if (index_desc.empty()) { | |||
| if (index_desc.first.empty()) { | |||
| odev.copy_from_fixlayout(inp); | |||
| } else { | |||
| if (index_desc[0].vec.layout[0]) { | |||
| if (!index_desc.second) { | |||
| // only call megdnn exec if result is not empty | |||
| this->megdnn_opr(*this).exec( | |||
| inp.as_megdnn(), index_desc, odev.as_megdnn(), | |||
| inp.as_megdnn(), index_desc.first, odev.as_megdnn(), | |||
| intl::get_megdnn_workspace_from_var(output(1))); | |||
| } else { | |||
| mgb_assert(odev.empty()); | |||
| @@ -391,7 +395,11 @@ void intl::IndexingModifyMultiAxisVecHelper<Opr>::scn_do_execute() { | |||
| auto inp = this->fancy_indexing_get_tensors_for_modify_in_scn_do_execute(); | |||
| auto index_desc = this->make_megdnn_index_desc( | |||
| inp.first.layout().ndim, ShouldWarnOnScalarIndexer<Opr>::val); | |||
| if (index_desc.empty()) { | |||
| if (index_desc.second){ | |||
| mgb_assert(inp.second.shape().is_empty()); | |||
| return; | |||
| } | |||
| if (index_desc.first.empty()) { | |||
| using IMT = IndexingModifyType; | |||
| static constexpr auto modify_type = | |||
| IndexingModifyTypeGetter<Opr>::value; | |||
| @@ -410,11 +418,28 @@ void intl::IndexingModifyMultiAxisVecHelper<Opr>::scn_do_execute() { | |||
| } else { | |||
| this->megdnn_opr(*this).exec( | |||
| inp.first.as_megdnn(), inp.second.as_megdnn(), | |||
| index_desc, | |||
| index_desc.first, | |||
| intl::get_megdnn_workspace_from_var(output(1))); | |||
| } | |||
| } | |||
| template<class Opr> | |||
| cg::OperatorNodeBase::NodeProp* | |||
| intl::IndexingModifyMultiAxisVecHelper<Opr>::do_make_node_prop() const { | |||
| auto prop = Super::do_make_node_prop(); | |||
| using DT = NodeProp::DepType; | |||
| // TODO: should also allow input shape is empty if any | |||
| // indexer's shape is empty | |||
| prop->add_dep_type_existing_var(input(1), DT::VALUE_ALLOW_EMPTY); | |||
| for (auto i: m_input2idxonly_axis_indexer) { | |||
| if (i) { | |||
| prop->add_dep_type_existing_var( | |||
| i->idx.node(), DT::VALUE_ALLOW_EMPTY); | |||
| } | |||
| } | |||
| return prop; | |||
| } | |||
| template<class Opr> | |||
| void intl::IndexingModifyMultiAxisVecHelper<Opr>:: | |||
| add_input_layout_constraint() { | |||
| @@ -429,7 +454,6 @@ add_input_layout_constraint() { | |||
| MGB_IMPL_FANCY_INDEXING_OPR_GET( | |||
| IndexingMultiAxisVec, "indexing_multi_axis_vec", false, | |||
| output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||
| output(1)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||
| ); | |||
| MGB_IMPL_FANCY_INDEXING_OPR_MODIFY( | |||
| IndexingSetMultiAxisVec, "indexing_set_multi_axis_vec", false); | |||
| @@ -469,12 +493,10 @@ MGB_IMPL_OPR_GRAD(IndexingIncrMultiAxisVec) { | |||
| MGB_IMPL_FANCY_INDEXING_OPR_GET( | |||
| MeshIndexing, "mesh_indexing", false, | |||
| output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||
| output(1)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE);); | |||
| output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE);); | |||
| MGB_IMPL_FANCY_INDEXING_OPR_GET( | |||
| BatchedMeshIndexing, "batched_mesh_indexing", false, | |||
| output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||
| output(1)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE);); | |||
| output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE);); | |||
| MGB_IMPL_OPR_GRAD(MeshIndexing) { | |||
| if (wrt_idx != 0) { | |||
| @@ -117,7 +117,9 @@ namespace intl { | |||
| protected: | |||
| using Super::Super; | |||
| const megdnn::IndexingMultiAxisVec::IndexDesc& | |||
| //! return IndexDesc and whether it has an AxisIndexer with | |||
| //! empty shape | |||
| std::pair<const megdnn::IndexingMultiAxisVec::IndexDesc&, bool> | |||
| make_megdnn_index_desc( | |||
| size_t inp_ndim, bool warn_all_scalar = true); | |||
| }; | |||
| @@ -130,6 +132,7 @@ namespace intl { | |||
| void init_output_static_infer_desc() override final; | |||
| void scn_do_execute() override final; | |||
| NodeProp* do_make_node_prop() const override; | |||
| void add_input_layout_constraint() override final; | |||
| protected: | |||
| @@ -649,17 +649,6 @@ namespace { | |||
| > TernaryTraitTypes; | |||
| TYPED_TEST_CASE(TestOprBasicArithTernaryElemwise, TernaryTraitTypes); | |||
| ::testing::AssertionResult assert_shape_equal(const TensorShape& v0, | |||
| const TensorShape& v1) { | |||
| if (v0.eq_shape(v1)) | |||
| return ::testing::AssertionSuccess() | |||
| << v0.to_string() << " == " << v1.to_string(); | |||
| else | |||
| return ::testing::AssertionFailure() | |||
| << v0.to_string() << " != " << v1.to_string(); | |||
| } | |||
| #define ASSERT_SHAPE_EQ(v0, v1) ASSERT_TRUE(assert_shape_equal(v0, v1)) | |||
| } // anonymous namespace | |||
| template<typename Trait, typename dtype> | |||
| @@ -974,14 +963,14 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputUnary) { | |||
| ASSERT_NO_THROW(func->execute().wait()); | |||
| ASSERT_TRUE(host_y.empty()); | |||
| ASSERT_TRUE(host_y.shape().is_empty()); | |||
| ASSERT_SHAPE_EQ(host_y.shape(), TensorShape({3, 0, 1, 3})); | |||
| MGB_ASSERT_SHAPE_EQ(host_y.shape(), TensorShape({3, 0, 1, 3})); | |||
| } | |||
| TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | |||
| HostTensorGenerator<> gen; | |||
| auto graph = ComputingGraph::make(); | |||
| auto host_x = gen({0, 8, 1, 7}), host_y = gen({0, 8, 1, 7}); | |||
| auto x = opr::Host2DeviceCopy::make(*graph, host_x), | |||
| y = opr::Host2DeviceCopy::make(*graph, host_y), | |||
| z = x + y; | |||
| @@ -997,14 +986,14 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | |||
| ASSERT_NO_THROW(func->execute().wait()); | |||
| ASSERT_TRUE(host_z.empty()); | |||
| ASSERT_TRUE(host_z.shape().is_empty()); | |||
| ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 0, 7})); | |||
| MGB_ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 0, 7})); | |||
| // Broadcast to 0 (2) | |||
| host_y->resize({2, 8, 1, 7}); | |||
| ASSERT_NO_THROW(func->execute().wait()); | |||
| ASSERT_TRUE(host_z.empty()); | |||
| ASSERT_TRUE(host_z.shape().is_empty()); | |||
| ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 1, 7})); | |||
| MGB_ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 1, 7})); | |||
| // Scalar broadcast | |||
| z = x + x.make_scalar(1.f); | |||
| @@ -1012,7 +1001,7 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | |||
| ASSERT_NO_THROW(func->execute().wait()); | |||
| ASSERT_TRUE(host_z.empty()); | |||
| ASSERT_TRUE(host_z.shape().is_empty()); | |||
| ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 1, 7})); | |||
| MGB_ASSERT_SHAPE_EQ(host_z.shape(), TensorShape({0, 8, 1, 7})); | |||
| } | |||
| // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} | |||
| @@ -14,6 +14,7 @@ | |||
| #include "megbrain/opr/basic_arith.h" | |||
| #include "megbrain/opr/io.h" | |||
| #include "megbrain/opr/misc.h" | |||
| #include "megbrain/opr/utility.h" | |||
| #include "megbrain/test/autocheck.h" | |||
| #include "megbrain/test/helper.h" | |||
| #include "megbrain/test/megdnn_helper.h" | |||
| @@ -1195,6 +1196,92 @@ TEST(TestOprIndexing, SetMeshIndexing) { | |||
| } | |||
| } | |||
| namespace { | |||
| template<class Opr> | |||
| void run_multi_axis_vec_empty_shape( | |||
| const TensorShape& ishp, const TensorShape& idx0, | |||
| const TensorShape& idx1, const TensorShape& tshp) { | |||
| mgb_assert(ishp.ndim >= 4); | |||
| mgb_assert(idx0.is_empty() || idx1.is_empty()); | |||
| using AI = opr::indexing::AxisIndexer; | |||
| auto graph = ComputingGraph::make(); | |||
| HostTensorGenerator<> gen; | |||
| HostTensorGenerator<dtype::Int32> gen_idx; | |||
| auto host_x = gen(ishp); | |||
| auto x = opr::Host2DeviceCopy::make(*graph, host_x), | |||
| idx_dynamic_shape = opr::MarkDynamicVar::make( | |||
| opr::ImmutableTensor::make(*graph, *gen_idx(idx0))), | |||
| idx_static_shape = | |||
| opr::ImmutableTensor::make(*graph, *gen_idx(idx1)), | |||
| y = Opr::make(x, { | |||
| AI::make_interval(-1, None, None, x.make_scalar(2)), | |||
| AI::make_index(1, idx_dynamic_shape), | |||
| AI::make_index(2, idx_static_shape)}); | |||
| HostTensorND host_y; | |||
| auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
| func->execute(); | |||
| ASSERT_TRUE(host_y.shape().is_empty()); | |||
| MGB_ASSERT_SHAPE_EQ(host_y.shape(), tshp); | |||
| } | |||
| template<class Opr> | |||
| void run_modify_multi_axis_vec_empty_shape( | |||
| const TensorShape& ishp, const TensorShape& vshp, | |||
| const TensorShape& idx0, const TensorShape& idx1) { | |||
| mgb_assert(ishp.ndim >= 4); | |||
| mgb_assert(vshp.is_empty() && (idx0.is_empty() || idx1.is_empty())); | |||
| using AI = opr::indexing::AxisIndexer; | |||
| auto graph = ComputingGraph::make(); | |||
| HostTensorGenerator<> gen; | |||
| HostTensorGenerator<dtype::Int32> gen_idx; | |||
| auto host_x = gen(ishp), host_v = gen(vshp); | |||
| auto x = opr::Host2DeviceCopy::make(*graph, host_x), | |||
| v = opr::Host2DeviceCopy::make(*graph, host_v), | |||
| idx_dynamic_shape = opr::MarkDynamicVar::make( | |||
| opr::ImmutableTensor::make(*graph, *gen_idx(idx0))), | |||
| idx_static_shape = | |||
| opr::ImmutableTensor::make(*graph, *gen_idx(idx1)), | |||
| y = Opr::make(x, v, { | |||
| AI::make_interval(-1, None, None, x.make_scalar(2)), | |||
| AI::make_index(1, idx_dynamic_shape), | |||
| AI::make_index(2, idx_static_shape)}); | |||
| HostTensorND host_y; | |||
| auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
| func->execute(); | |||
| MGB_ASSERT_TENSOR_EQ(*host_x, host_y); | |||
| } | |||
| } | |||
| TEST(TestOprIndexing, MultiAxisVecEmptyShape) { | |||
| TensorShape ishp{8, 2, 3, 4}; | |||
| size_t n = ishp[0], last_ndim = ishp[ishp.ndim - 1] / 2; | |||
| run_multi_axis_vec_empty_shape<opr::IndexingMultiAxisVec>( | |||
| ishp, {0}, {0}, {n, 0, last_ndim}); | |||
| run_multi_axis_vec_empty_shape<opr::MeshIndexing>( | |||
| ishp, {0}, {2}, {n, 0, 2, last_ndim}); | |||
| run_multi_axis_vec_empty_shape<opr::MeshIndexing>( | |||
| ishp, {3}, {0}, {n, 3, 0, last_ndim}); | |||
| run_multi_axis_vec_empty_shape<opr::BatchedMeshIndexing>( | |||
| ishp, {n, 0}, {n, 2}, {n, 0, 2, last_ndim}); | |||
| run_multi_axis_vec_empty_shape<opr::BatchedMeshIndexing>( | |||
| ishp, {n, 4}, {n, 0}, {n, 4, 0, last_ndim}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::IndexingIncrMultiAxisVec>( | |||
| ishp, {n, 0, last_ndim}, {0}, {0}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::IndexingSetMultiAxisVec>( | |||
| ishp, {n, 0, last_ndim}, {0}, {0}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::IncrMeshIndexing>( | |||
| ishp, {n, 0, 2, last_ndim}, {0}, {2}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::SetMeshIndexing>( | |||
| ishp, {n, 3, 0, last_ndim}, {3}, {0}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::BatchedIncrMeshIndexing>( | |||
| ishp, {n, 4, 0, last_ndim}, {n, 4}, {n, 0}); | |||
| run_modify_multi_axis_vec_empty_shape<opr::BatchedSetMeshIndexing>( | |||
| ishp, {n, 0, 5, last_ndim}, {n, 0}, {n, 5}); | |||
| } | |||
| #endif // MGB_ENABLE_EXCEPTION | |||
| // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} | |||
| @@ -158,6 +158,16 @@ namespace mgb { | |||
| return ::testing::AssertionSuccess(); | |||
| } | |||
| ::testing::AssertionResult mgb::__assert_shape_equal(const TensorShape& v0, | |||
| const TensorShape& v1) { | |||
| if (v0.eq_shape(v1)) | |||
| return ::testing::AssertionSuccess() | |||
| << v0.to_string() << " == " << v1.to_string(); | |||
| else | |||
| return ::testing::AssertionFailure() | |||
| << v0.to_string() << " != " << v1.to_string(); | |||
| } | |||
| #if WIN32 | |||
| #include <io.h> | |||
| #include <fcntl.h> | |||
| @@ -133,6 +133,11 @@ decltype(auto) container_to_vector(Container &&ct) { | |||
| #define MGB_ASSERT_TENSOR_EQ(v0, v1) \ | |||
| MGB_ASSERT_TENSOR_NEAR(v0, v1, 1e-6) | |||
| ::testing::AssertionResult __assert_shape_equal(const TensorShape& v0, | |||
| const TensorShape& v1); | |||
| #define MGB_ASSERT_SHAPE_EQ(v0, v1) \ | |||
| ASSERT_TRUE(::mgb::__assert_shape_equal(v0, v1)) | |||
| /*! | |||
| * \brief xorshift+ RNG, which is very fast | |||