GitOrigin-RevId: db4eba5877
tags/v0.5.0
@@ -226,17 +226,19 @@ void mixin::IndexingMultiAxisVecMegDNNOprHolder<Opr>::record_megdnn_opr( | |||||
} | } | ||||
/* ==================== MultiAxisVecFancyIndexingHelper ==================== */ | /* ==================== MultiAxisVecFancyIndexingHelper ==================== */ | ||||
const megdnn::IndexingMultiAxisVec::IndexDesc& | |||||
std::pair<const megdnn::IndexingMultiAxisVec::IndexDesc&, bool> | |||||
intl::MultiAxisVecFancyIndexingHelper::make_megdnn_index_desc( | intl::MultiAxisVecFancyIndexingHelper::make_megdnn_index_desc( | ||||
size_t inp_ndim, bool warn_all_scalar) { | size_t inp_ndim, bool warn_all_scalar) { | ||||
auto &&index = m_megdnn_index_cache; | auto &&index = m_megdnn_index_cache; | ||||
index.clear(); | index.clear(); | ||||
bool is_empty_shape = false; | |||||
for (auto i: reverse_adaptor(m_input2idxonly_axis_indexer)) { | for (auto i: reverse_adaptor(m_input2idxonly_axis_indexer)) { | ||||
if (i) { | if (i) { | ||||
index.push_back({ | index.push_back({ | ||||
i->axis.get(inp_ndim), | i->axis.get(inp_ndim), | ||||
i->idx.node()->dev_tensor().as_megdnn()}); | 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; | m_scalar_idx_warn_printed = true; | ||||
} | } | ||||
return index; | |||||
return {index, is_empty_shape}; | |||||
} | } | ||||
/* ==================== IndexingMultiAxisVecBase ==================== */ | /* ==================== IndexingMultiAxisVecBase ==================== */ | ||||
@@ -272,6 +274,8 @@ template<class Opr> | |||||
cg::OperatorNodeBase::NodeProp* | cg::OperatorNodeBase::NodeProp* | ||||
IndexingMultiAxisVecBase<Opr>::do_make_node_prop() const { | IndexingMultiAxisVecBase<Opr>::do_make_node_prop() const { | ||||
auto prop = Super::do_make_node_prop(); | 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) { | for (auto i: m_input2idxonly_axis_indexer) { | ||||
if (i) { | if (i) { | ||||
prop->add_dep_type_existing_var( | prop->add_dep_type_existing_var( | ||||
@@ -360,13 +364,13 @@ void IndexingMultiAxisVecBase<Opr>::scn_do_execute() { | |||||
auto &&index_desc = make_megdnn_index_desc( | auto &&index_desc = make_megdnn_index_desc( | ||||
inp.layout().ndim, ShouldWarnOnScalarIndexer<Opr>::val); | inp.layout().ndim, ShouldWarnOnScalarIndexer<Opr>::val); | ||||
auto &&odev = output(0)->dev_tensor(); | auto &&odev = output(0)->dev_tensor(); | ||||
if (index_desc.empty()) { | |||||
if (index_desc.first.empty()) { | |||||
odev.copy_from_fixlayout(inp); | odev.copy_from_fixlayout(inp); | ||||
} else { | } else { | ||||
if (index_desc[0].vec.layout[0]) { | |||||
if (!index_desc.second) { | |||||
// only call megdnn exec if result is not empty | // only call megdnn exec if result is not empty | ||||
this->megdnn_opr(*this).exec( | 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))); | intl::get_megdnn_workspace_from_var(output(1))); | ||||
} else { | } else { | ||||
mgb_assert(odev.empty()); | 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 inp = this->fancy_indexing_get_tensors_for_modify_in_scn_do_execute(); | ||||
auto index_desc = this->make_megdnn_index_desc( | auto index_desc = this->make_megdnn_index_desc( | ||||
inp.first.layout().ndim, ShouldWarnOnScalarIndexer<Opr>::val); | 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; | using IMT = IndexingModifyType; | ||||
static constexpr auto modify_type = | static constexpr auto modify_type = | ||||
IndexingModifyTypeGetter<Opr>::value; | IndexingModifyTypeGetter<Opr>::value; | ||||
@@ -410,12 +418,29 @@ void intl::IndexingModifyMultiAxisVecHelper<Opr>::scn_do_execute() { | |||||
} else { | } else { | ||||
this->megdnn_opr(*this).exec( | this->megdnn_opr(*this).exec( | ||||
inp.first.as_megdnn(), inp.second.as_megdnn(), | inp.first.as_megdnn(), inp.second.as_megdnn(), | ||||
index_desc, | |||||
index_desc.first, | |||||
intl::get_megdnn_workspace_from_var(output(1))); | intl::get_megdnn_workspace_from_var(output(1))); | ||||
} | } | ||||
} | } | ||||
template<class Opr> | 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>:: | void intl::IndexingModifyMultiAxisVecHelper<Opr>:: | ||||
add_input_layout_constraint() { | add_input_layout_constraint() { | ||||
auto check_cont1 = [](const TensorLayout &ly) { | auto check_cont1 = [](const TensorLayout &ly) { | ||||
@@ -429,7 +454,6 @@ add_input_layout_constraint() { | |||||
MGB_IMPL_FANCY_INDEXING_OPR_GET( | MGB_IMPL_FANCY_INDEXING_OPR_GET( | ||||
IndexingMultiAxisVec, "indexing_multi_axis_vec", false, | IndexingMultiAxisVec, "indexing_multi_axis_vec", false, | ||||
output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | ||||
output(1)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||||
); | ); | ||||
MGB_IMPL_FANCY_INDEXING_OPR_MODIFY( | MGB_IMPL_FANCY_INDEXING_OPR_MODIFY( | ||||
IndexingSetMultiAxisVec, "indexing_set_multi_axis_vec", false); | IndexingSetMultiAxisVec, "indexing_set_multi_axis_vec", false); | ||||
@@ -469,12 +493,10 @@ MGB_IMPL_OPR_GRAD(IndexingIncrMultiAxisVec) { | |||||
MGB_IMPL_FANCY_INDEXING_OPR_GET( | MGB_IMPL_FANCY_INDEXING_OPR_GET( | ||||
MeshIndexing, "mesh_indexing", false, | 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( | MGB_IMPL_FANCY_INDEXING_OPR_GET( | ||||
BatchedMeshIndexing, "batched_mesh_indexing", false, | 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) { | MGB_IMPL_OPR_GRAD(MeshIndexing) { | ||||
if (wrt_idx != 0) { | if (wrt_idx != 0) { | ||||
@@ -117,7 +117,9 @@ namespace intl { | |||||
protected: | protected: | ||||
using Super::Super; | 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( | make_megdnn_index_desc( | ||||
size_t inp_ndim, bool warn_all_scalar = true); | size_t inp_ndim, bool warn_all_scalar = true); | ||||
}; | }; | ||||
@@ -130,6 +132,7 @@ namespace intl { | |||||
void init_output_static_infer_desc() override final; | void init_output_static_infer_desc() override final; | ||||
void scn_do_execute() override final; | void scn_do_execute() override final; | ||||
NodeProp* do_make_node_prop() const override; | |||||
void add_input_layout_constraint() override final; | void add_input_layout_constraint() override final; | ||||
protected: | protected: | ||||
@@ -649,17 +649,6 @@ namespace { | |||||
> TernaryTraitTypes; | > TernaryTraitTypes; | ||||
TYPED_TEST_CASE(TestOprBasicArithTernaryElemwise, 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 | } // anonymous namespace | ||||
template<typename Trait, typename dtype> | template<typename Trait, typename dtype> | ||||
@@ -974,14 +963,14 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputUnary) { | |||||
ASSERT_NO_THROW(func->execute().wait()); | ASSERT_NO_THROW(func->execute().wait()); | ||||
ASSERT_TRUE(host_y.empty()); | ASSERT_TRUE(host_y.empty()); | ||||
ASSERT_TRUE(host_y.shape().is_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) { | TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | ||||
HostTensorGenerator<> gen; | HostTensorGenerator<> gen; | ||||
auto graph = ComputingGraph::make(); | auto graph = ComputingGraph::make(); | ||||
auto host_x = gen({0, 8, 1, 7}), host_y = gen({0, 8, 1, 7}); | auto host_x = gen({0, 8, 1, 7}), host_y = gen({0, 8, 1, 7}); | ||||
auto x = opr::Host2DeviceCopy::make(*graph, host_x), | auto x = opr::Host2DeviceCopy::make(*graph, host_x), | ||||
y = opr::Host2DeviceCopy::make(*graph, host_y), | y = opr::Host2DeviceCopy::make(*graph, host_y), | ||||
z = x + y; | z = x + y; | ||||
@@ -997,14 +986,14 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | |||||
ASSERT_NO_THROW(func->execute().wait()); | ASSERT_NO_THROW(func->execute().wait()); | ||||
ASSERT_TRUE(host_z.empty()); | ASSERT_TRUE(host_z.empty()); | ||||
ASSERT_TRUE(host_z.shape().is_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) | // Broadcast to 0 (2) | ||||
host_y->resize({2, 8, 1, 7}); | host_y->resize({2, 8, 1, 7}); | ||||
ASSERT_NO_THROW(func->execute().wait()); | ASSERT_NO_THROW(func->execute().wait()); | ||||
ASSERT_TRUE(host_z.empty()); | ASSERT_TRUE(host_z.empty()); | ||||
ASSERT_TRUE(host_z.shape().is_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 | // Scalar broadcast | ||||
z = x + x.make_scalar(1.f); | z = x + x.make_scalar(1.f); | ||||
@@ -1012,7 +1001,7 @@ TEST(TestOprBasicArithElemwise, EmptyInputOutputBinary) { | |||||
ASSERT_NO_THROW(func->execute().wait()); | ASSERT_NO_THROW(func->execute().wait()); | ||||
ASSERT_TRUE(host_z.empty()); | ASSERT_TRUE(host_z.empty()); | ||||
ASSERT_TRUE(host_z.shape().is_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}}} | // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} |
@@ -14,6 +14,7 @@ | |||||
#include "megbrain/opr/basic_arith.h" | #include "megbrain/opr/basic_arith.h" | ||||
#include "megbrain/opr/io.h" | #include "megbrain/opr/io.h" | ||||
#include "megbrain/opr/misc.h" | #include "megbrain/opr/misc.h" | ||||
#include "megbrain/opr/utility.h" | |||||
#include "megbrain/test/autocheck.h" | #include "megbrain/test/autocheck.h" | ||||
#include "megbrain/test/helper.h" | #include "megbrain/test/helper.h" | ||||
#include "megbrain/test/megdnn_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 | #endif // MGB_ENABLE_EXCEPTION | ||||
// vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} | // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} |
@@ -158,6 +158,16 @@ namespace mgb { | |||||
return ::testing::AssertionSuccess(); | 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 | #if WIN32 | ||||
#include <io.h> | #include <io.h> | ||||
#include <fcntl.h> | #include <fcntl.h> | ||||
@@ -133,6 +133,11 @@ decltype(auto) container_to_vector(Container &&ct) { | |||||
#define MGB_ASSERT_TENSOR_EQ(v0, v1) \ | #define MGB_ASSERT_TENSOR_EQ(v0, v1) \ | ||||
MGB_ASSERT_TENSOR_NEAR(v0, v1, 1e-6) | 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 | * \brief xorshift+ RNG, which is very fast | ||||