blob: 4283821ccfb3deb7a1e256b66b1c7055eacfa288 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file np_boolean_assign.cc
* \brief CPU implementation of Boolean Mask Assign
*/
#include "../../common/utils.h"
#include "../contrib/boolean_mask-inl.h"
namespace mxnet {
namespace op {
template <bool scalar = false>
struct BooleanAssignCPUKernel {
private:
static size_t bin_search(const size_t* idx, const size_t idx_size, const size_t i) {
size_t left = 0, right = idx_size, mid = (left + right) / 2;
while (left != right) {
if (idx[mid] == i + 1) {
if (idx[mid - 1] == i) {
mid -= 1;
break;
} else if (idx[mid - 1] == i + 1) {
right = mid;
mid = (left + right) / 2;
}
} else if (idx[mid] == i) {
if (idx[mid + 1] == i + 1) {
break;
} else {
left = mid;
mid = (left + right + 1) / 2;
}
} else if (idx[mid] < i + 1) {
left = mid;
mid = (left + right + 1) / 2;
} else if (idx[mid] > i + 1) {
right = mid;
mid = (left + right) / 2;
}
}
return mid;
}
public:
template <typename DType>
static void Map(int i,
DType* data,
const size_t* idx,
const size_t idx_size,
const size_t leading,
const size_t middle,
const size_t trailing,
const DType val) {
// binary search for the turning point
size_t mid = bin_search(idx, idx_size, i);
// final answer is in mid
for (size_t l = 0; l < leading; ++l) {
for (size_t t = 0; t < trailing; ++t) {
data[(l * middle + mid) * trailing + t] = val;
}
}
}
template <typename DType>
static void Map(int i,
DType* data,
const size_t* idx,
const size_t idx_size,
const size_t leading,
const size_t middle,
const size_t valid_num,
const size_t trailing,
DType* tensor,
const bool broadcast = false) {
// binary search for the turning point
size_t mid = bin_search(idx, idx_size, i);
// final answer is in mid
for (size_t l = 0; l < leading; ++l) {
for (size_t t = 0; t < trailing; ++t) {
if (scalar) {
data[(l * middle + mid) * trailing + t] = tensor[0];
} else {
data[(l * middle + mid) * trailing + t] =
(broadcast) ? tensor[l * trailing + t] : tensor[(l * valid_num + i) * trailing + t];
}
}
}
}
};
bool BooleanAssignShape(const nnvm::NodeAttrs& attrs,
mxnet::ShapeVector* in_attrs,
mxnet::ShapeVector* out_attrs) {
CHECK(in_attrs->size() == 2U || in_attrs->size() == 3U);
CHECK_EQ(out_attrs->size(), 1U);
CHECK(shape_is_known(in_attrs->at(0)) && shape_is_known(in_attrs->at(1)))
<< "shape of both input and mask should be known";
const TShape& dshape = in_attrs->at(0);
const TShape& mshape = in_attrs->at(1);
const int start_axis = std::stoi(common::attr_value_string(attrs, "start_axis", "0"));
for (int i = 0; i < mshape.ndim(); ++i) {
CHECK_EQ(dshape[i + start_axis], mshape[i])
<< "boolean index did not match indexed array along dimension " << i + start_axis
<< "; dimension is " << dshape[i + start_axis] << " but corresponding boolean dimension is "
<< mshape[i];
}
// check if output shape is the same as the input data
SHAPE_ASSIGN_CHECK(*out_attrs, 0, dshape);
// for tensor version, the tensor should have less than 1 dimension
if (in_attrs->size() == 3U) {
if (mshape.ndim() == dshape.ndim()) {
CHECK_LE(in_attrs->at(2).ndim(), 1U)
<< "boolean array indexing assignment requires a 0 or 1-dimensional input, input has "
<< in_attrs->at(2).ndim() << " dimensions";
} else {
const TShape& vshape = in_attrs->at(2);
if (vshape.Size() > 1) {
for (int i = 0; i < start_axis; ++i) {
CHECK_EQ(dshape[i], vshape[i])
<< "shape mismatch of value with input at dimension " << i << "; dimension is "
<< dshape[i] << " but corresponding value dimension is " << vshape[i];
}
for (int i = 1; i <= (dshape.ndim() - start_axis - mshape.ndim()); ++i) {
CHECK_EQ(dshape[dshape.ndim() - i], vshape[vshape.ndim() - i])
<< "shape mismatch of value with input at dimension " << (dshape.ndim() - i)
<< "; dimension is " << dshape[dshape.ndim() - i]
<< " but corresponding value dimension is " << vshape[vshape.ndim() - 1];
}
}
}
}
return shape_is_known(out_attrs->at(0));
}
bool BooleanAssignType(const nnvm::NodeAttrs& attrs,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK(in_attrs->size() == 2U || in_attrs->size() == 3U);
CHECK_EQ(out_attrs->size(), 1U);
// input and output should always have the same type
TYPE_ASSIGN_CHECK(*out_attrs, 0, in_attrs->at(0));
TYPE_ASSIGN_CHECK(*in_attrs, 0, out_attrs->at(0));
if (in_attrs->size() == 3U) {
// if tensor version, the tensor should also have the same type as input
TYPE_ASSIGN_CHECK(*in_attrs, 2, in_attrs->at(0));
TYPE_ASSIGN_CHECK(*in_attrs, 0, in_attrs->at(2));
CHECK_NE(in_attrs->at(2), -1);
}
return out_attrs->at(0) != -1 && in_attrs->at(0) != -1 && in_attrs->at(1) != -1;
}
// calculate the number of valid (masked) values, also completing the prefix_sum vector
template <typename DType>
size_t GetValidNumCPU(const DType* idx, size_t* prefix_sum, const size_t idx_size) {
prefix_sum[0] = 0;
for (size_t i = 0; i < idx_size; i++) {
prefix_sum[i + 1] = prefix_sum[i] + ((idx[i]) ? 1 : 0);
}
return prefix_sum[idx_size];
}
void NumpyBooleanAssignForwardCPU(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK(inputs.size() == 2U || inputs.size() == 3U);
CHECK_EQ(outputs.size(), 1U);
CHECK_EQ(req.size(), 1U);
CHECK_EQ(req[0], kWriteInplace) << "Only WriteInplace is supported for npi_boolean_assign";
Stream<cpu>* s = ctx.get_stream<cpu>();
const TBlob& data = inputs[0];
const TShape& dshape = data.shape_;
const TBlob& mask = inputs[1];
const TShape& mshape = mask.shape_;
const int start_axis = std::stoi(common::attr_value_string(attrs, "start_axis", "0"));
// Get valid_num
size_t valid_num = 0;
size_t mask_size = mask.shape_.Size();
std::vector<size_t> prefix_sum(mask_size + 1, 0);
MSHADOW_TYPE_SWITCH_WITH_BOOL(mask.type_flag_, MType, {
valid_num = GetValidNumCPU(mask.dptr<MType>(), prefix_sum.data(), mask_size);
});
// If there's no True in mask, return directly
if (valid_num == 0)
return;
if (inputs.size() == 3U) {
// tensor case
const TShape& vshape = inputs.at(2).shape_;
if (inputs[2].shape_.Size() != 1) {
auto vndim = vshape.ndim();
auto dndim = dshape.ndim();
auto mndim = mshape.ndim();
CHECK(vndim <= (dndim - mndim + 1));
if ((vndim == (dndim - mndim + 1)) && (vshape[start_axis] != 1)) {
// tensor case, check tensor size equal to or broadcastable with valid_num
CHECK_EQ(static_cast<size_t>(valid_num), vshape[start_axis])
<< "boolean array indexing assignment cannot assign " << vshape
<< " input values to the " << valid_num << " output values where the mask is true"
<< std::endl;
}
}
}
size_t leading = 1U;
size_t middle = mask_size;
size_t trailing = 1U;
for (int i = 0; i < dshape.ndim(); ++i) {
if (i < start_axis) {
leading *= dshape[i];
}
if (i >= start_axis + mshape.ndim()) {
trailing *= dshape[i];
}
}
if (inputs.size() == 3U) {
// tensor case
const TShape& vshape = inputs.at(2).shape_;
MSHADOW_TYPE_SWITCH_WITH_BOOL(data.type_flag_, DType, {
if (inputs[2].shape_.Size() == 1) {
Kernel<BooleanAssignCPUKernel<true>, cpu>::Launch(s,
valid_num,
data.dptr<DType>(),
prefix_sum.data(),
prefix_sum.size(),
leading,
middle,
valid_num,
trailing,
inputs[2].dptr<DType>());
} else {
bool need_broadcast = (vshape.ndim() == (dshape.ndim() - mshape.ndim() + 1)) ?
(vshape[start_axis] == 1) :
true;
Kernel<BooleanAssignCPUKernel<false>, cpu>::Launch(s,
valid_num,
data.dptr<DType>(),
prefix_sum.data(),
prefix_sum.size(),
leading,
middle,
valid_num,
trailing,
inputs[2].dptr<DType>(),
need_broadcast);
}
});
} else {
// scalar case
CHECK(attrs.dict.find("value") != attrs.dict.end()) << "value needs be provided";
MSHADOW_TYPE_SWITCH_WITH_BOOL(data.type_flag_, DType, {
Kernel<BooleanAssignCPUKernel<true>, cpu>::Launch(
s,
valid_num,
data.dptr<DType>(),
prefix_sum.data(),
prefix_sum.size(),
leading,
middle,
trailing,
static_cast<DType>(std::stod(attrs.dict.at("value"))));
});
}
}
NNVM_REGISTER_OP(_npi_boolean_mask_assign_scalar)
.describe(R"code(Scalar version of boolean assign)code" ADD_FILELINE)
.set_num_inputs(2)
.set_num_outputs(1)
.set_attr<nnvm::FListInputNames>("FListInputNames",
[](const NodeAttrs& attrs) {
return std::vector<std::string>{"data", "mask"};
})
.set_attr<mxnet::FInferShape>("FInferShape", BooleanAssignShape)
.set_attr<nnvm::FInferType>("FInferType", BooleanAssignType)
.set_attr<FCompute>("FCompute<cpu>", NumpyBooleanAssignForwardCPU)
.set_attr<nnvm::FInplaceOption>("FInplaceOption",
[](const NodeAttrs& attrs) {
return std::vector<std::pair<int, int> >{{0, 0}};
})
.set_attr<FResourceRequest>("FResourceRequest",
[](const NodeAttrs& attrs) {
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace};
})
.set_attr<nnvm::FGradient>("FGradient", MakeZeroGradNodes)
.add_argument("data", "NDArray-or-Symbol", "input")
.add_argument("mask", "NDArray-or-Symbol", "mask")
.add_argument("value", "float", "value to be assigned to masked positions")
.add_argument("start_axis", "int", "starting axis of boolean mask");
NNVM_REGISTER_OP(_npi_boolean_mask_assign_tensor)
.describe(R"code(Tensor version of boolean assign)code" ADD_FILELINE)
.set_num_inputs(3)
.set_num_outputs(1)
.set_attr<nnvm::FListInputNames>("FListInputNames",
[](const NodeAttrs& attrs) {
return std::vector<std::string>{"data", "mask", "value"};
})
.set_attr<mxnet::FInferShape>("FInferShape", BooleanAssignShape)
.set_attr<nnvm::FInferType>("FInferType", BooleanAssignType)
.set_attr<FCompute>("FCompute<cpu>", NumpyBooleanAssignForwardCPU)
.set_attr<nnvm::FInplaceOption>("FInplaceOption",
[](const NodeAttrs& attrs) {
return std::vector<std::pair<int, int> >{{0, 0}};
})
.set_attr<FResourceRequest>("FResourceRequest",
[](const NodeAttrs& attrs) {
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace};
})
.set_attr<nnvm::FGradient>("FGradient", MakeZeroGradNodes)
.add_argument("data", "NDArray-or-Symbol", "input")
.add_argument("mask", "NDArray-or-Symbol", "mask")
.add_argument("value", "NDArray-or-Symbol", "assignment")
.add_argument("start_axis", "int", "starting axis of boolean mask");
} // namespace op
} // namespace mxnet