blob: 1c05bf07d218077234370ed0cb089143dc905285 [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 shuffle_op.cc
* \brief Operator to shuffle elements of an NDArray
*/
#if ((__GNUC__ > 4 && !defined(__clang__major__)) || (__clang_major__ > 4 && __linux__)) && \
defined(_OPENMP) && !defined(__ANDROID__)
#define USE_GNU_PARALLEL_SHUFFLE
#endif
#include <mxnet/operator_util.h>
#include <numeric>
#include <algorithm>
#include <random>
#include <vector>
#include <cstring>
#ifdef USE_GNU_PARALLEL_SHUFFLE
#include <unistd.h>
#include <parallel/algorithm>
#endif
#include "../elemwise_op_common.h"
namespace mxnet {
namespace op {
namespace {
template <typename DType, typename Rand>
void Shuffle1D(DType* const out, const index_t size, Rand* const prnd) {
#ifdef USE_GNU_PARALLEL_SHUFFLE
/*
* See issue #15029: the data type of n needs to be compatible with
* the gcc library: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B\
* -v3/include/parallel/random_shuffle.h#L384
*/
auto rand_n = [prnd](uint32_t n) {
std::uniform_int_distribution<uint32_t> dist(0, n - 1);
return dist(*prnd);
};
__gnu_parallel::random_shuffle(out, out + size, rand_n);
#else
std::shuffle(out, out + size, *prnd);
#endif
}
template <typename DType, typename Rand>
void ShuffleND(DType* const out,
DType* const in,
const index_t size,
const index_t first_axis_len,
Rand* const prnd,
const OpContext& ctx,
OpReqType reqT) {
// Optimized Fisher-Yates shuffling
using namespace mxnet_op;
const index_t stride = size / first_axis_len;
CHECK_GT(first_axis_len, 0U);
const size_t stride_bytes = sizeof(DType) * stride;
std::vector<index_t> index(first_axis_len);
std::iota(index.begin(), index.end(), 0);
std::shuffle(index.begin(), index.end(), *prnd);
if (reqT != kWriteInplace) {
for (index_t i = 0; i < first_axis_len; ++i) {
auto j = index[i];
void* const dst = static_cast<void* const>(out + stride * j);
void* const src = static_cast<void* const>(in + stride * i);
std::memcpy(dst, src, stride_bytes);
}
} else {
assert(in == out);
std::vector<bool> done(first_axis_len, false);
Tensor<cpu, 1, DType> tmp_buf =
ctx.requested[1].get_space_typed<cpu, 1, DType>(Shape1(stride), ctx.get_stream<cpu>());
void* const tmp = static_cast<void*>(tmp_buf.dptr_);
for (index_t i = 0; i < first_axis_len; ++i) {
if (!done[i]) {
index_t pos = index[i];
if (pos != i) {
void* const dst = static_cast<void*>(out + stride * i);
void* const src = static_cast<void*>(out + stride * pos);
std::memcpy(tmp, dst, stride_bytes);
std::memcpy(dst, src, stride_bytes);
done[i] = true;
void* dst_loop = static_cast<void*>(out + stride * pos);
// go through indexes until return to the starting one
while (index[pos] != i) {
const index_t next_pos = index[pos];
void* src_loop = static_cast<void*>(out + stride * next_pos);
std::memcpy(dst_loop, src_loop, stride_bytes);
done[pos] = true;
dst_loop = src_loop;
pos = next_pos;
}
std::memcpy(dst_loop, tmp, stride_bytes);
done[pos] = true;
}
}
}
}
}
} // namespace
void ShuffleForwardCPU(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mxnet_op;
if (req[0] == kNullOp) {
return;
}
CHECK_NE(req[0], kAddTo) << "Shuffle does not support AddTo";
const mxnet::TShape& input_shape = inputs[0].shape_;
const index_t size = inputs[0].Size();
const index_t first_axis_len = input_shape[0];
Stream<cpu>* s = ctx.get_stream<cpu>();
MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, DType, {
Tensor<cpu, 1, DType> in = inputs[0].get_with_shape<cpu, 1, DType>(Shape1(size), s);
Tensor<cpu, 1, DType> out = outputs[0].get_with_shape<cpu, 1, DType>(Shape1(size), s);
auto& prnd = ctx.requested[0].get_random<cpu, index_t>(ctx.get_stream<cpu>())->GetRndEngine();
if (input_shape.ndim() == 1) {
if (req[0] != kWriteInplace) {
std::copy(in.dptr_, in.dptr_ + size, out.dptr_);
}
Shuffle1D(out.dptr_, size, &prnd);
} else {
ShuffleND(out.dptr_, in.dptr_, size, first_axis_len, &prnd, ctx, req[0]);
}
});
}
// No parameter is declared.
// No backward computation is registered. Shuffling is not differentiable.
NNVM_REGISTER_OP(_shuffle)
.add_alias("shuffle")
.add_alias("_npi_shuffle")
.describe(R"code(Randomly shuffle the elements.
This shuffles the array along the first axis.
The order of the elements in each subarray does not change.
For example, if a 2D array is given, the order of the rows randomly changes,
but the order of the elements in each row does not change.
)code")
.set_num_inputs(1)
.set_num_outputs(1)
.set_attr<mxnet::FInferShape>("FInferShape", ElemwiseShape<1, 1>)
.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<1, 1>)
.set_attr<FResourceRequest>("FResourceRequest",
[](const nnvm::NodeAttrs& attrs) {
return std::vector<ResourceRequest>{ResourceRequest::kRandom,
ResourceRequest::kTempSpace};
})
.set_attr<nnvm::FInplaceOption>("FInplaceOption",
[](const NodeAttrs& attrs) {
return std::vector<std::pair<int, int>>{{0, 0}};
})
.set_attr<FCompute>("FCompute<cpu>", ShuffleForwardCPU)
.add_argument("data", "NDArray-or-Symbol", "Data to be shuffled.");
} // namespace op
} // namespace mxnet