#include "megbrain/opr/misc.h"
#include "megbrain/opr/basic_arith_wrapper.h"
#include "megbrain/opr/blas.h"
#include "megbrain/opr/io.h"
#include "megbrain/opr/tensor_manip.h"
#include "megbrain/opr/utility.h"
#include "megbrain/test/autocheck.h"
#include "megbrain/test/helper.h"
#include "megbrain/test/megdnn_helper.h"
#include <numeric>
#include <random>
using namespace mgb;
namespace {
void shape_abc(const TensorShape& shape, size_t axis, size_t& A, size_t& B, size_t& C) {
auto acc_mul = [](const size_t* first, const size_t* last) {
return std::accumulate(first, last, 1u, std::multiplies<size_t>());
};
A = acc_mul(shape.shape, shape.shape + axis);
B = shape.shape[axis];
C = acc_mul(shape.shape + axis + 1, shape.shape + shape.ndim);
}
void argsort_data_gen(HostTensorND& dest) {
mgb_assert(dest.layout().ndim == 2 && dest.layout().is_contiguous());
size_t m = dest.layout()[0], n = dest.layout()[1];
auto ptr = dest.ptr<float>();
RNGxorshf rng{next_rand_seed()};
std::uniform_real_distribution<float> dist_base{-10.f, 10.f},
dist_delta{0.1f, 1.2f};
for (size_t i = 0; i < m; ++i) {
auto v = dist_base(rng);
for (size_t j = 0; j < n; ++j) {
ptr[j] = v;
v += dist_delta(rng);
}
std::shuffle(ptr, ptr + n, rng);
ptr += n;
}
}
}
TEST(TestOprMisc, Argmxx) {
auto run = [](bool is_max, int32_t axis, TensorShape sshape) {
auto dshape = sshape;
dshape.shape[axis] = 1;
using Checker = AutoOprChecker<1, 1>;
auto make_graph =
[&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
if (is_max)
return {opr::Argmax::make(inputs[0], {axis})};
else
return {opr::Argmin::make(inputs[0], {axis})};
};
auto better_than = [&](float curr, float best) {
if (is_max)
return curr > best;
else
return curr < best;
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
out[0].dtype(dtype::Int32()).resize(dshape);
size_t A, B, C;
shape_abc(sshape, axis, A, B, C);
for (size_t a = 0; a < A; ++a)
for (size_t c = 0; c < C; ++c) {
float best_val;
size_t best_arg = -1;
if (is_max)
best_val = std::numeric_limits<float>::lowest();
else
best_val = std::numeric_limits<float>::max();
for (size_t b = 0; b < B; ++b) {
float curr_val = inp[0]->ptr<float>()[(a * B + b) * C + c];
if (better_than(curr_val, best_val)) {
best_val = curr_val;
best_arg = b;
}
}
out[0].ptr<int>()[a * C + c] = best_arg;
}
};
Checker{make_graph, fwd}
.set_input_allow_grad(0, false)
.set_output_allow_grad(0, false)
.run({sshape})
.run({sshape})
.run({sshape});
};
run(true, 0, {5});
run(true, 1, {2, 3, 4, 5});
run(true, 2, {2, 3, 4, 5});
run(true, 3, {2, 3, 4, 5});
run(false, 0, {3, 4, 5});
run(false, 1, {2, 3, 4, 5});
run(false, 2, {2, 3, 4, 5});
run(false, 3, {2, 3, 4, 5});
}
TEST(TestOprMisc, Argsort) {
using Order = opr::Argsort::Param::Order;
auto run = [](Order order) {
using Checker = AutoOprChecker<1, 2>;
auto make_graph =
[&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
return opr::Argsort::make(inputs[0], order);
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
size_t m = inp[0]->shape()[0], n = inp[0]->shape()[1];
auto pi = inp[0]->ptr<float>();
auto poval = out[0].resize({m, n}).ptr<float>();
auto poidx = out[1].resize({m, n}).ptr<int>();
using KV = std::pair<float, int>;
std::vector<KV> row(n);
for (size_t i = 0; i < m; ++i) {
for (size_t j = 0; j < n; ++j) {
row[j].first = pi[i * n + j];
row[j].second = j;
}
if (order == Order::ASCENDING) {
std::sort(row.begin(), row.end());
} else {
std::sort(row.begin(), row.end(), std::greater<KV>{});
}
for (size_t j = 0; j < n; ++j) {
poval[i * n + j] = row[j].first;
poidx[i * n + j] = row[j].second;
}
}
};
Checker::RunOptions opt;
opt.numdiff_eps = 0.045;
Checker{make_graph, fwd}
.set_input_generator(0, argsort_data_gen)
.set_output_allow_grad(1, false)
.run({TensorShape{1, 1}}, opt)
.run({TensorShape{5, 3}}, opt)
.run({TensorShape{10, 24}}, opt);
};
run(Order::ASCENDING);
run(Order::DESCENDING);
}
TEST(TestOprMisc, Cumsum) {
using Param = opr::Cumsum::Param;
auto run = [](const Param& param) {
using Checker = AutoOprChecker<1, 1>;
auto make_graph =
[&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
return {opr::Cumsum::make(inputs[0], param)};
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
out[0].resize(inp[0]->shape());
auto pin = inp[0]->ptr<float>(), pout = out[0].ptr<float>();
size_t A, B, C;
int real_axis = param.axis;
if (real_axis < 0)
real_axis += 3;
shape_abc(inp[0]->shape(), real_axis, A, B, C);
ptrdiff_t stride = C;
if (param.reverse)
stride = -stride;
for (size_t i = 0; i < A; ++i) {
for (size_t k = 0; k < C; ++k) {
auto pi = pin + i * B * C + k, po = pout + i * B * C + k;
if (param.reverse) {
pi += (B - 1) * C;
po += (B - 1) * C;
}
if (param.exclusive) {
*po = 0;
po += stride;
}
float sum = 0;
for (size_t j = 0; j < B - 1; ++j) {
sum += pi[j * stride];
po[j * stride] = sum;
}
if (!param.exclusive) {
po[(B - 1) * stride] = sum + pi[(B - 1) * stride];
}
}
}
};
Checker{make_graph, fwd}
.run({TensorShape{2, 3, 4}})
.run({TensorShape{3, 1, 2}})
.run({TensorShape{4, 2, 3}});
};
for (int32_t axis = -3; axis < 3; ++axis)
for (int mask = 0; mask < 4; ++mask)
run({axis, bool(mask >> 1), bool(mask & 1)});
}
TEST(TestOprMisc, CondTake) {
using Param = opr::CondTake::Param;
using Checker = AutoOprChecker<2, 1>;
auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
return {opr::CondTake::make(inputs[0], inputs[1], {Param::Mode::LT})[0]};
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
std::vector<float> values;
auto data = inp[0]->ptr<float>(), mask = inp[1]->ptr<float>();
auto isize = inp[0]->shape().total_nr_elems();
for (size_t i = 0; i < isize; ++i) {
if (mask[i] < 0) {
values.push_back(data[i]);
}
}
out[0].resize({values.size()});
memcpy(out[0].ptr<float>(), values.data(), sizeof(float) * values.size());
};
auto ensure_nonempty = [](Checker::NumInpArray inp) {
auto mask = inp[1]->ptr<float>();
auto isize = inp[1]->shape().total_nr_elems();
for (size_t i = 0; i < isize; ++i) {
if (mask[i] < 0)
return;
}
mask[isize - 1] = -1;
};
auto mki = [](const TensorShape& shp) -> Checker::ShapeInpArray {
return {shp, shp};
};
Checker{make_graph, fwd}
.set_input_allow_grad(1, false)
.set_input_coordinator(ensure_nonempty)
.run(mki({2}))
.run(mki({3, 5, 8}))
.run(mki({100}));
}
TEST(TestOprMisc, CondTakeEmptyIO) {
using Param = opr::CondTake::Param;
HostTensorGenerator<> gen;
auto check = [&](const TensorShape& shp) {
auto host_x = gen(shp);
auto graph = ComputingGraph::make();
auto x = opr::Host2DeviceCopy::make(*graph, host_x);
auto y = x + 1;
auto out = opr::CondTake::make(x, y, {Param::Mode::EQ});
HostTensorND host_out0, host_out1;
auto func = graph->compile(
{make_callback_copy(out[0], host_out0),
make_callback_copy(out[1], host_out1)});
func->execute();
ASSERT_EQ(TensorShape{0}, host_out0.shape());
ASSERT_EQ(TensorShape{0}, host_out1.shape());
};
check({1});
check({0});
check({1, 0});
}
TEST(TestOprMisc, TopKValueOnly) {
auto run = [](bool dyn_k, bool non_contig) {
using Checker = AutoOprChecker<1, 1>;
std::shared_ptr<HostTensorND> host_k;
SymbolVar var_x0, var_x1;
auto make_graph =
[&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
auto k = opr::Host2DeviceCopy::make(
*inputs[0].node()->owner_graph(), host_k);
if (dyn_k) {
k = opr::MarkDynamicVar::make(k);
}
auto x = inputs[0];
if (non_contig) {
var_x0 = x;
x = opr::Subtensor::make(
x, {opr::Subtensor::AxisIndexer::make_interval(
1, None, opr::GetVarShape::make(x, 1) / 2, None)});
var_x1 = x;
}
auto outs = opr::TopK::make(x, k, opr::TopK::Param::Mode::KTH_ONLY);
return {outs[0]};
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
auto opr = megdnn_naive_handle()->create_operator<megdnn::TopK>();
int k = host_k->ptr<int>()[0];
HostTensorND x = *inp[0];
if (non_contig) {
auto layout = x.layout();
layout.shape[1] /= 2;
x = x.sub(SubTensorSpec::make_from_layout(layout));
}
TensorLayout outl0, outl1;
opr->deduce_layout(k, x.layout(), outl0, outl1);
size_t wk_size = opr->get_workspace_in_bytes(k, x.layout(), outl0, outl1);
std::unique_ptr<dt_byte[]> wk_store{new dt_byte[wk_size]};
opr->exec(
k, x.as_megdnn(), out[0].resize(outl0).as_megdnn(), {},
{wk_store.get(), wk_size});
};
Checker checker{make_graph, fwd};
checker.set_input_generator(0, argsort_data_gen);
host_k = std::make_shared<HostTensorND>(
checker.comp_node(), TensorShape{1}, dtype::Int32{});
host_k->ptr<int>()[0] = 1;
Checker::RunOptions opt;
opt.numdiff_eps = 0.047;
auto invoke = [&](int k, size_t m, size_t n) {
host_k->ptr<int>()[0] = k;
checker.run({TensorShape{m, n}}, opt);
};
if (!non_contig) {
invoke(1, 1, 1);
}
invoke(-2, 3, 2);
invoke(-1, 4, 5);
invoke(3, 10, 33);
invoke(-8, 23, 35);
if (non_contig) {
ASSERT_EQ(prev_dev_ptr(var_x0), prev_dev_ptr(var_x1));
}
};
for (auto i : {false, true}) {
for (auto j : {false, true}) {
run(i, j);
}
}
}
TEST(TestOprMisc, TopKSorted) {
using Checker = AutoOprChecker<1, 2>;
std::shared_ptr<HostTensorND> host_k;
auto constexpr mode = opr::TopK::Param::Mode::VALUE_IDX_SORTED;
auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray {
auto k = opr::Host2DeviceCopy::make(*inputs[0].node()->owner_graph(), host_k);
auto x = inputs[0];
return opr::TopK::make(x, k, mode);
};
auto fwd = [&](Checker::NumOutArray& out, Checker::NumInpArray inp) {
auto opr = megdnn_naive_handle()->create_operator<megdnn::TopK>();
opr->param().mode = mode;
int k = host_k->ptr<int>()[0];
TensorLayout outl0, outl1;
opr->deduce_layout(k, inp[0]->layout(), outl0, outl1);
size_t wk_size = opr->get_workspace_in_bytes(k, inp[0]->layout(), outl0, outl1);
std::unique_ptr<dt_byte[]> wk_store{new dt_byte[wk_size]};
opr->exec(
k, inp[0]->as_megdnn(), out[0].resize(outl0).as_megdnn(),
out[1].resize(outl1).as_megdnn(), {wk_store.get(), wk_size});
};
Checker checker{make_graph, fwd};
checker.set_input_generator(0, argsort_data_gen).set_output_allow_grad(1, false);
host_k = std::make_shared<HostTensorND>(
checker.comp_node(), TensorShape{1}, dtype::Int32{});
host_k->ptr<int>()[0] = 1;
Checker::RunOptions opt;
opt.numdiff_eps = 0.047;
auto invoke = [&](int k, size_t m, size_t n) {
host_k->ptr<int>()[0] = k;
checker.run({TensorShape{m, n}}, opt);
};
invoke(1, 1, 1);
invoke(-1, 3, 5);
invoke(5, 13, 23);
invoke(-8, 35, 4);
}
TEST(TestOprMisc, TopKSortedIdxOnly) {
HostTensorGenerator<> gen;
auto graph = ComputingGraph::make();
std::shared_ptr<HostTensorND> host_x = gen({2, 5});
std::shared_ptr<HostTensorND> host_y = gen({2, 5});
for (size_t i = 0; i < 10; ++i) {
host_y->ptr<float>()[i] = 0.0f;
}
auto x = opr::Host2DeviceCopy::make(*graph, host_x),
idx = opr::TopK::make(
x, x.make_scalar(3), opr::TopK::Param::Mode::VALUE_IDX_SORTED)[1],
y = opr::TypeCvt::make(idx, dtype::Float32{}),
gx = cg::grad(opr::reduce_sum(y, y.make_scalar(1)), x);
HostTensorND host_gx;
auto func = graph->compile({make_callback_copy(gx, host_gx)});
func->execute();
MGB_ASSERT_TENSOR_EQ(host_gx, *host_y);
}
TEST(TestOprMisc, TopKGrad) {
HostTensorGenerator<> gen;
auto graph = ComputingGraph::make();
std::shared_ptr<HostTensorND> host_x = gen({2, 5});
std::shared_ptr<HostTensorND> host_k = gen({1});
host_k->ptr<float>()[0] = 3;
auto x = opr::Host2DeviceCopy::make(*graph, host_x),
k = opr::Host2DeviceCopy::make(*graph, host_k),
ki = opr::TypeCvt::make(k, dtype::Int32{}),
val = opr::TopK::make(x, ki, opr::TopK::Param::Mode::VALUE_IDX_SORTED)[0],
gk = cg::grad(opr::reduce_sum(val, val.make_scalar(1)), ki, true, false);
EXPECT_TRUE(gk == nullptr);
}