@@ -15,6 +15,7 @@ from utils import make_tensor | |||
import megengine | |||
import megengine.core.tensor.megbrain_graph as G | |||
import megengine.functional as F | |||
import megengine.jit as jit | |||
from megengine.core._imperative_rt.core2 import apply | |||
from megengine.core._trace_option import use_symbolic_shape | |||
from megengine.core.ops import builtin | |||
@@ -584,3 +585,26 @@ def test_advance_indexing_with_bool(test_varnode): | |||
np.testing.assert_equal( | |||
a[:, b, 0:2, [True, False]], aa[:, bb, 0:2, [True, False]].numpy() | |||
) | |||
@pytest.mark.parametrize("symbolic", [True, False, None]) | |||
def test_subtensor_on_empty_tensor(symbolic): | |||
np_x = np.array([], dtype=np.float32).reshape(10, 0, 10) | |||
mge_x = megengine.tensor(np_x) | |||
def run_test(fn): | |||
out_ref = fn(np_x) | |||
if symbolic is not None: | |||
fn = jit.trace(symbolic=symbolic)(fn) | |||
for i in range(3): | |||
out = fn(mge_x) | |||
np.testing.assert_equal(out.numpy(), out_ref) | |||
run_test(lambda x: x[0:1, :, :]) | |||
run_test(lambda x: x[1:100:2, :, :]) | |||
run_test(lambda x: x[-10:5:2, :, :]) | |||
run_test(lambda x: x[5:1:-1, :, :]) | |||
run_test(lambda x: x[3, 10:1:1, 5]) | |||
run_test(lambda x: x[3, 10:1:1, 5:-1]) | |||
run_test(lambda x: x[:100, :100, :100]) | |||
run_test(lambda x: x[100:200, 300:400, 500:600]) |
@@ -133,27 +133,42 @@ SubTensorSpec Slice::apply(TensorLayout layout, int axis) const { | |||
return "None"; | |||
return std::to_string(v.val()); | |||
}; | |||
auto mod_size = [size_ax](ptrdiff_t v) { | |||
auto mod_size = [size_ax](ptrdiff_t v)->ptrdiff_t { | |||
if (size_ax == 0) return 0; | |||
return v < 0 ? v + size_ax : v; | |||
}; | |||
MGB_MARK_USED_VAR(tostr); | |||
#define CHECK(cond) \ | |||
mgb_assert(cond, \ | |||
"index out of bound: layout=%s; request begin=%s end=%s step=%s " \ | |||
"axis=%d", \ | |||
layout.to_string().c_str(), tostr(m_begin).c_str(), \ | |||
tostr(m_end).c_str(), tostr(m_step).c_str(), axis) | |||
#define CHECK(cond) \ | |||
if (m_is_scalar_idx) { \ | |||
mgb_assert(cond, \ | |||
"index out of bound: layout=%s; request index=%s, axis=%d", \ | |||
layout.to_string().c_str(), tostr(m_begin).c_str(), axis); \ | |||
} else { \ | |||
mgb_assert(cond, \ | |||
"index out of bound: layout=%s; request begin=%s end=%s step=%s " \ | |||
"axis=%d", \ | |||
layout.to_string().c_str(), tostr(m_begin).c_str(), \ | |||
tostr(m_end).c_str(), tostr(m_step).c_str(), axis); \ | |||
} | |||
if (step > 0) { | |||
begin = mod_size(m_begin.val_with_default(0)); | |||
end = mod_size(m_end.val_with_default(size_ax)); | |||
CHECK(begin >= 0 && end >= begin && end <= size_ax); | |||
if (!m_is_scalar_idx) { | |||
end = std::min(end, size_ax); | |||
begin = std::min(begin, end); | |||
} | |||
CHECK(begin >= 0 && end >= begin && end <= size_ax) | |||
} else { | |||
begin = mod_size(m_begin.val_with_default(size_ax - 1)); | |||
end = m_end.valid() ? mod_size(m_end.val()) : -1; | |||
if (!m_is_scalar_idx) { | |||
begin = std::min(begin, std::max<ptrdiff_t>(size_ax-1, 0)); | |||
end = std::min(end, begin); | |||
} | |||
CHECK(step < 0 && begin >= 0 && end <= begin && begin < size_ax && | |||
end >= -1); | |||
end >= -1) | |||
} | |||
auto step_abs = std::abs(step); | |||
layout.shape[axis] = (std::abs(end - begin) + step_abs - 1) / step_abs; | |||
@@ -83,16 +83,20 @@ class SubTensorSpec { | |||
/*! | |||
* \brief slice along some axis; index as in Python, with negative indices | |||
* supported | |||
* supported. Scalar index can also be represented as a Slice, where | |||
* m_begin = idx, m_end = idx+1 and m_step = 1. The flag m_is_scalar_idx | |||
* indicates whether the Slice comes from a scalar index. | |||
*/ | |||
class Slice { | |||
Maybe<ptrdiff_t> m_begin, m_end, m_step; | |||
bool m_is_scalar_idx; | |||
public: | |||
Slice(Maybe<ptrdiff_t> begin = None, | |||
Maybe<ptrdiff_t> end = None, | |||
Maybe<ptrdiff_t> step = None): | |||
m_begin{begin}, m_end{end}, m_step{step} | |||
Maybe<ptrdiff_t> step = None, | |||
bool is_scalar_idx = false): | |||
m_begin{begin}, m_end{end}, m_step{step}, m_is_scalar_idx{is_scalar_idx} | |||
{ } | |||
/*! | |||
@@ -178,7 +178,9 @@ SubTensorSpec FancyIndexingHelper::do_make_sub_spec( | |||
i.axis.get_raw(), axis); | |||
prev_axis = axis; | |||
Maybe<ptrdiff_t> begin, end, step; | |||
bool is_scalar_idx = false; | |||
if (i.idx.node()) { | |||
is_scalar_idx = true; | |||
if (!m_require_scalar_index) { | |||
continue; | |||
} | |||
@@ -195,7 +197,7 @@ SubTensorSpec FancyIndexingHelper::do_make_sub_spec( | |||
step = next_iv(); | |||
} | |||
spec.merge_with(Slice(begin, end, step).apply(spec.layout(), axis)); | |||
spec.merge_with(Slice(begin, end, step, is_scalar_idx).apply(spec.layout(), axis)); | |||
} | |||
mgb_assert(iv_iter == m_value_infer_result.end()); | |||
@@ -660,7 +660,19 @@ MGB_IMPL_OPR_GRAD(AxisAddRemove) { | |||
/* f{{{ ======================= Subtensor ======================= */ | |||
MGB_IMPL_FANCY_INDEXING_OPR_GET(Subtensor, "subtensor", true); | |||
Subtensor::Subtensor(VarNode *inp, const IndexDesc &desc, | |||
const OperatorNodeConfig &config): | |||
Super({inp->owner_graph(), config, "subtensor", {inp}}, | |||
inp, nullptr, desc, true) { | |||
output(0)->add_flag(VarNode::Flag::ALLOW_EMPTY_SHAPE); | |||
} | |||
SymbolVar Subtensor::make(SymbolVar inp, const IndexDesc &desc, | |||
const OperatorNodeConfig &config) { | |||
return inp.insert_single_output_opr<Subtensor>(inp.node(), desc, config); | |||
} | |||
MGB_DYN_TYPE_OBJ_FINAL_IMPL(Subtensor); | |||
#if MGB_ENABLE_GRAD | |||
MGB_IMPL_OPR_GRAD(Subtensor) { | |||
@@ -722,6 +734,13 @@ void Subtensor::init_rt_force_dynamic_mem_alloc_imply_chain() { | |||
out->add_rt_force_dynamic_mem_alloc_imply_chain(inp); | |||
} | |||
Subtensor::NodeProp* Subtensor::do_make_node_prop() const { | |||
auto ret = Super::do_make_node_prop(); | |||
ret->add_dep_type_existing_var(input(0), | |||
NodeProp::DepType::VALUE_ALLOW_EMPTY); | |||
return ret; | |||
} | |||
// f}}} | |||
/* f{{{ ================== ModifySubtensorImplHelper ================== */ | |||
@@ -358,6 +358,7 @@ MGB_DEFINE_OPR_CLASS(Subtensor, | |||
void scn_do_execute() override; | |||
void mem_plan_fwd_in2out_readonly() override; | |||
void init_rt_force_dynamic_mem_alloc_imply_chain() override; | |||
NodeProp* do_make_node_prop() const override; | |||
public: | |||
Subtensor(VarNode *inp, const IndexDesc &desc, | |||
@@ -894,6 +894,47 @@ TEST(TestTensorManip, SubtensorIdxChange) { | |||
run(false); | |||
} | |||
TEST(TestTensorManip, SubtensorEmptyIO) { | |||
using AIdx = opr::Subtensor::AxisIndexer; | |||
using IndexDesc = std::vector<AIdx>; | |||
using IndexDescCreater = thin_function<IndexDesc(SymbolVar)>; | |||
HostTensorGenerator<> gen; | |||
auto run = [&](const TensorShape& inp_shp, const TensorShape& out_shp, const IndexDescCreater& c) { | |||
auto host_x = gen(inp_shp); | |||
auto graph = ComputingGraph::make(); | |||
auto x = opr::Host2DeviceCopy::make(*graph, host_x); | |||
auto y = opr::Subtensor::make(x, c(x)); | |||
HostTensorND host_y; | |||
auto func = graph->compile({make_callback_copy(y, host_y)}); | |||
func->execute(); | |||
ASSERT_EQ(host_y.shape(), out_shp); | |||
ASSERT_TRUE(host_y.empty()); | |||
}; | |||
// x.shape = {0}, x[:0] | |||
run({0}, {0}, [&](SymbolVar x)->IndexDesc { | |||
return {AIdx::make_interval(0, None, x.make_scalar(0), None)}; | |||
}); | |||
// x.shape = {100, 0}, x[0:-10:2] | |||
run({100, 0}, {45, 0}, [&](SymbolVar x)->IndexDesc { | |||
return {AIdx::make_interval(0, x.make_scalar(0), x.make_scalar(-10), x.make_scalar(2))}; | |||
}); | |||
// x.shape = {100, 0}, x[10:-10:2, 0:0] | |||
run({100, 0}, {40, 0}, [&](SymbolVar x)->IndexDesc { | |||
return {AIdx::make_interval(0, x.make_scalar(10), x.make_scalar(-10), x.make_scalar(2)), | |||
AIdx::make_interval(1, x.make_scalar(0), x.make_scalar(0), None)}; | |||
}); | |||
// x.shape = {10, 0, 10}, x[5, 10:-10:-2] | |||
run({10, 0, 10}, {0, 10}, [&](SymbolVar x)->IndexDesc { | |||
return {AIdx::make_index(0, x.make_scalar(5)), | |||
AIdx::make_interval(1, x.make_scalar(10), x.make_scalar(-10), x.make_scalar(2))}; | |||
}); | |||
// x.shape = {10}, x[100:] | |||
run({10}, {0}, [&](SymbolVar x)->IndexDesc { | |||
return {AIdx::make_interval(0, x.make_scalar(100), None, None)}; | |||
}); | |||
} | |||
namespace { | |||
void test_subtensor_fwdonly(bool dyn_inp, bool dyn_idx) { | |||