442 lines
14 KiB
C++
442 lines
14 KiB
C++
/* ******************************************************************************
|
|
*
|
|
*
|
|
* This program and the accompanying materials are made available under the
|
|
* terms of the Apache License, Version 2.0 which is available at
|
|
* https://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* See the NOTICE file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
* 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.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
******************************************************************************/
|
|
|
|
//
|
|
// @author raver119@gmail.com, created on 07.10.2017.
|
|
// @author Yurii Shyrma (iuriish@yahoo.com)
|
|
//
|
|
|
|
#include <array/NDArray.h>
|
|
#include <helpers/Loops.h>
|
|
|
|
#include <helpers/shape.h>
|
|
#include <ops/declarable/CustomOperations.h>
|
|
#include <ops/specials.h>
|
|
#include <types/types.h>
|
|
|
|
namespace sd {
|
|
|
|
/**
|
|
* @brief Checks if the shape of NDArray contains 1 before(order c) or after(order f) the specified axis
|
|
*
|
|
* @param input
|
|
* @param axis
|
|
* @return int
|
|
*/
|
|
SD_INLINE int isShapeExtendedWithOnes(NDArray&input, LongType axis) {
|
|
bool isAllOne = true;
|
|
auto shapes = shape::shapeOf(input.shapeInfo());
|
|
auto rank = input.rankOf();
|
|
if (rank > axis) {
|
|
if (input.ordering() == 'c') {
|
|
// check before the axis
|
|
for (sd::LongType i = 0; i < axis; i++) {
|
|
isAllOne = isAllOne && (shapes[i] == 1);
|
|
}
|
|
} else {
|
|
// check after the axis
|
|
for (int i = axis + 1; i < rank; i++) {
|
|
isAllOne = isAllOne && (shapes[i] == 1);
|
|
}
|
|
}
|
|
return isAllOne;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
struct InputArgsCase2 {
|
|
const T *ptr;
|
|
int size;
|
|
};
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::concatCpuGeneric(const std::vector<NDArray *> &inArrs, NDArray &output,
|
|
const LongType axis) {
|
|
const sd::LongType numOfInArrs = inArrs.size();
|
|
const auto sizeofT = output.sizeOfT();
|
|
|
|
T *zBuff = output.bufferAsT<T>();
|
|
|
|
bool shapeExtendedWithOnes = isShapeExtendedWithOnes(output, axis);
|
|
bool followEws1 = false;
|
|
bool matchesOutputOrdering = true;
|
|
for (int i = 0; i < numOfInArrs; ++i) {
|
|
shapeExtendedWithOnes = shapeExtendedWithOnes && isShapeExtendedWithOnes(*inArrs[i], axis);
|
|
matchesOutputOrdering = matchesOutputOrdering && inArrs[i]->ordering() == output.ordering();
|
|
}
|
|
|
|
bool copyCaseEws1 = followEws1 & matchesOutputOrdering;
|
|
bool copyCase1 = numOfInArrs > 1 ? copyCaseEws1 & shapeExtendedWithOnes : copyCaseEws1;
|
|
|
|
if (copyCase1) {
|
|
// copyCase1:
|
|
// When NdArrays follow the same order and unit elementwise stride and
|
|
// the concantneation axis is 0th or has only 1 before it {1, 1, ..., axis} for "c"
|
|
// or axis is (rank-1)th or has only 1 after it {axis, 1, 1, ..., 1} for "f"
|
|
// we will concatenate them by sequential copying of the whole buffers
|
|
|
|
std::vector<T *> zPtrList;
|
|
T *z = output.bufferAsT<T>();
|
|
for (sd::LongType i = 0; i < numOfInArrs; i++) {
|
|
zPtrList.push_back(z);
|
|
z += inArrs[i]->lengthOf();
|
|
}
|
|
auto func = [&inArrs, &zPtrList](sd::LongType thread_id, sd::LongType start, sd::LongType stop,
|
|
sd::LongType increment) -> void {
|
|
for (sd::LongType i = start; i < stop; ++i) {
|
|
const auto memAmountToCopy = inArrs[i]->lengthOf();
|
|
const auto inputPtr = inArrs[i]->bufferAsT<T>();
|
|
|
|
auto zPtr = zPtrList[i];
|
|
for (int j = 0; j < memAmountToCopy; j++) {
|
|
zPtr[j] = inputPtr[j];
|
|
}
|
|
}
|
|
};
|
|
|
|
samediff::Threads::parallel_tad(func, 0, numOfInArrs, 1);
|
|
return;
|
|
}
|
|
|
|
// for one Array
|
|
if (numOfInArrs < 2) {
|
|
output.assign(inArrs[0]);
|
|
return;
|
|
}
|
|
bool copyCase2 = copyCaseEws1 && output.ordering() == 'c';
|
|
if (copyCase2) {
|
|
sd::LongType times = 1;
|
|
auto shapes = shape::shapeOf(output.shapeInfo());
|
|
|
|
T *z = output.bufferAsT<T>();
|
|
for (int i = 0; i < axis; i++) {
|
|
times = times * shapes[i];
|
|
}
|
|
|
|
sd::LongType totalCopySize = output.lengthOf() / times;
|
|
|
|
std::vector<InputArgsCase2<T>> inputArgs;
|
|
for (sd::LongType i = 0; i < numOfInArrs; i++) {
|
|
InputArgsCase2<T> input = {inArrs[i]->bufferAsT<T>(),
|
|
static_cast<int>(inArrs[i]->lengthOf()) / static_cast<int>(times)};
|
|
inputArgs.push_back(input);
|
|
}
|
|
|
|
auto func = [&inputArgs, z, totalCopySize](uint64_t thread_id, int64_t start, int64_t stop,
|
|
int64_t increment) -> void {
|
|
auto outPtr = &(z[start * totalCopySize]);
|
|
auto numOfInArrs = inputArgs.size();
|
|
for (int i = start; i < stop; i++) {
|
|
for (size_t j = 0; j < numOfInArrs; j++) {
|
|
auto inputCopySize = inputArgs[j].size;
|
|
const T *inputBasePtr = inputArgs[j].ptr;
|
|
auto inputPtr = &(inputBasePtr[i * inputCopySize]);
|
|
// copy
|
|
PRAGMA_OMP_SIMD
|
|
for (int k = 0; k < inputCopySize; k++) {
|
|
outPtr[k] = inputPtr[k];
|
|
}
|
|
outPtr += inputCopySize;
|
|
}
|
|
}
|
|
};
|
|
samediff::Threads::parallel_tad(func, 0, times, 1);
|
|
return;
|
|
}
|
|
|
|
// Cache shape and stride information for output
|
|
const sd::LongType zRank = shape::rank(output.shapeInfo());
|
|
const sd::LongType* zShape = shape::shapeOf(output.shapeInfo());
|
|
const sd::LongType* zStride = shape::stride(output.shapeInfo());
|
|
|
|
// Pre-cache input arrays' shape information
|
|
std::vector<const sd::LongType*> inShapes(numOfInArrs);
|
|
std::vector<const sd::LongType*> inStrides(numOfInArrs);
|
|
std::vector<sd::LongType> inRanks(numOfInArrs);
|
|
|
|
for (sd::LongType i = 0; i < numOfInArrs; i++) {
|
|
inRanks[i] = shape::rank(inArrs[i]->shapeInfo());
|
|
inShapes[i] = shape::shapeOf(inArrs[i]->shapeInfo());
|
|
inStrides[i] = shape::stride(inArrs[i]->shapeInfo());
|
|
}
|
|
|
|
// general case
|
|
auto func = PRAGMA_THREADS_FOR {
|
|
sd::LongType coords[SD_MAX_RANK], temp;
|
|
|
|
for (sd::LongType i = start; i < stop; i += increment) {
|
|
INDEX2COORDS(i, zRank, zShape, coords);
|
|
|
|
sd::LongType zOffset;
|
|
COORDS2INDEX(zRank, zStride, coords, zOffset);
|
|
|
|
sd::LongType inArrIdx = 0;
|
|
sd::LongType xDim = inArrs[inArrIdx]->sizeAt(axis);
|
|
|
|
temp = coords[axis];
|
|
while (coords[axis] >= xDim) {
|
|
coords[axis] -= xDim;
|
|
xDim = inArrs[++inArrIdx]->sizeAt(axis);
|
|
}
|
|
|
|
const T *x = inArrs[inArrIdx]->bufferAsT<T>();
|
|
sd::LongType xOffset;
|
|
COORDS2INDEX(inRanks[inArrIdx], inStrides[inArrIdx], coords, xOffset);
|
|
|
|
zBuff[zOffset] = x[xOffset];
|
|
|
|
coords[axis] = temp;
|
|
}
|
|
};
|
|
|
|
samediff::Threads::parallel_for(func, 0, output.lengthOf());
|
|
}
|
|
/**
|
|
* Concatneate multi array of the same shape together
|
|
* along a particular dimension
|
|
*/
|
|
template <typename T>
|
|
void SpecialMethods<T>::concatCpuGeneric(LongType dimension, int numArrays,NDArray **data,
|
|
NDArray *vresult) {
|
|
auto result = reinterpret_cast<T *>(vresult);
|
|
std::vector<NDArray *> inputs(numArrays);
|
|
|
|
|
|
for (sd::LongType i = 0; i < numArrays; ++i)
|
|
inputs[i] =
|
|
new NDArray(static_cast<void *>(data[i]), data[i]->shapeInfo(), nullptr, false, 0);
|
|
|
|
sd::SpecialMethods<T>::concatCpuGeneric(inputs, *vresult, dimension);
|
|
|
|
for (sd::LongType i = 0; i < numArrays; ++i) {
|
|
delete inputs[i];
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::splitCpuGeneric(NDArray& input, const std::vector<NDArray*>& outArrs, const LongType axis) {
|
|
int numSplits = outArrs.size();
|
|
const auto sizeofT = input.sizeOfT();
|
|
auto xBuff = input.bufferAsT<T>();
|
|
|
|
bool luckCase1 = ((axis == 0 && input.ordering() == 'c') || (axis == input.rankOf() - 1 && input.ordering() == 'f'));
|
|
|
|
if (luckCase1) {
|
|
T* x = const_cast<T*>(xBuff);
|
|
for (sd::LongType i = 0; i < numSplits; ++i) {
|
|
const auto memAmountToCopy = outArrs[i]->lengthOf();
|
|
ops::safe_copy(x, static_cast<const T*>(outArrs[i]->buffer()), static_cast<size_t>(memAmountToCopy));
|
|
x += memAmountToCopy;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Cache shape and stride information
|
|
const sd::LongType xRank = shape::rank(input.shapeInfo());
|
|
const sd::LongType* xShape = shape::shapeOf(input.shapeInfo());
|
|
const sd::LongType* xStride = shape::stride(input.shapeInfo());
|
|
|
|
// Pre-cache output array ranks, shapes, and strides
|
|
std::vector<const sd::LongType*> outShapes(numSplits);
|
|
std::vector<const sd::LongType*> outStrides(numSplits);
|
|
std::vector<sd::LongType> outRanks(numSplits);
|
|
|
|
for (int i = 0; i < numSplits; i++) {
|
|
outRanks[i] = shape::rank(outArrs[i]->shapeInfo());
|
|
outShapes[i] = shape::shapeOf(outArrs[i]->shapeInfo());
|
|
outStrides[i] = shape::stride(outArrs[i]->shapeInfo());
|
|
}
|
|
|
|
sd::LongType zDim = outArrs[0]->sizeAt(axis);
|
|
|
|
auto func = PRAGMA_THREADS_FOR {
|
|
sd::LongType coords[SD_MAX_RANK], temp;
|
|
|
|
for (sd::LongType i = start; i < stop; i += increment) {
|
|
INDEX2COORDS(i, xRank, xShape, coords);
|
|
sd::LongType xOffset;
|
|
COORDS2INDEX(xRank, xStride, coords, xOffset);
|
|
|
|
sd::LongType outArrIdx = 0;
|
|
temp = coords[axis];
|
|
|
|
while (coords[axis] >= zDim) {
|
|
coords[axis] -= zDim;
|
|
++outArrIdx;
|
|
}
|
|
|
|
T* z = outArrs[outArrIdx]->bufferAsT<T>();
|
|
sd::LongType zOffset;
|
|
COORDS2INDEX(outRanks[outArrIdx], outStrides[outArrIdx], coords, zOffset);
|
|
z[zOffset] = xBuff[xOffset];
|
|
|
|
coords[axis] = temp;
|
|
}
|
|
};
|
|
|
|
samediff::Threads::parallel_for(func, 0, input.lengthOf());
|
|
}
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::sortGeneric(NDArray *input, bool descending) {
|
|
auto x = input->bufferAsT<T>();
|
|
auto xShapeInfo = input->shapeInfo();
|
|
quickSort_parallel(input, Environment::getInstance().maxMasterThreads(), descending);
|
|
}
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::quickSort_parallel_internal(NDArray *x, int left, int right, int cutoff, bool descending) {
|
|
if (right - left <= cutoff) {
|
|
// Use insertion sort for small arrays
|
|
auto xBuff = x->bufferAsT<T>();
|
|
for (int i = left + 1; i <= right; i++) {
|
|
T key = xBuff[i];
|
|
int j = i - 1;
|
|
if (descending) {
|
|
while (j >= left && xBuff[j] < key) {
|
|
xBuff[j + 1] = xBuff[j];
|
|
j--;
|
|
}
|
|
} else {
|
|
while (j >= left && xBuff[j] > key) {
|
|
xBuff[j + 1] = xBuff[j];
|
|
j--;
|
|
}
|
|
}
|
|
xBuff[j + 1] = key;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Choose pivot as median of three
|
|
auto xBuff = x->bufferAsT<T>();
|
|
int mid = (left + right) / 2;
|
|
if (descending) {
|
|
if (xBuff[right] > xBuff[left]) std::swap(xBuff[right], xBuff[left]);
|
|
if (xBuff[mid] > xBuff[left]) std::swap(xBuff[mid], xBuff[left]);
|
|
if (xBuff[right] > xBuff[mid]) std::swap(xBuff[right], xBuff[mid]);
|
|
} else {
|
|
if (xBuff[right] < xBuff[left]) std::swap(xBuff[right], xBuff[left]);
|
|
if (xBuff[mid] < xBuff[left]) std::swap(xBuff[mid], xBuff[left]);
|
|
if (xBuff[right] < xBuff[mid]) std::swap(xBuff[right], xBuff[mid]);
|
|
}
|
|
|
|
// Partition
|
|
T pivot = xBuff[mid];
|
|
int i = left;
|
|
int j = right;
|
|
|
|
while (i <= j) {
|
|
if (descending) {
|
|
while (xBuff[i] > pivot) i++;
|
|
while (xBuff[j] < pivot) j--;
|
|
} else {
|
|
while (xBuff[i] < pivot) i++;
|
|
while (xBuff[j] > pivot) j--;
|
|
}
|
|
|
|
if (i <= j) {
|
|
std::swap(xBuff[i], xBuff[j]);
|
|
i++;
|
|
j--;
|
|
}
|
|
}
|
|
|
|
// Recursively sort sub-arrays
|
|
if (left < j) quickSort_parallel_internal(x, left, j, cutoff, descending);
|
|
if (i < right) quickSort_parallel_internal(x, i, right, cutoff, descending);
|
|
}
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::quickSort_parallel(NDArray *x, int numThreads, bool descending) {
|
|
const int CUTOFF = 32; // Threshold for switching to insertion sort
|
|
auto length = x->lengthOf();
|
|
|
|
if (length <= 1) return;
|
|
|
|
// For very small arrays, just use the internal sort
|
|
if (length <= CUTOFF || numThreads <= 1) {
|
|
quickSort_parallel_internal(x, 0, length - 1, CUTOFF, descending);
|
|
return;
|
|
}
|
|
|
|
// For larger arrays, partition into segments and sort in parallel
|
|
int segmentSize = length / numThreads;
|
|
auto func = PRAGMA_THREADS_FOR {
|
|
int threadLeft = start * segmentSize;
|
|
int threadRight = (start == numThreads - 1) ? length - 1 : (start + 1) * segmentSize - 1;
|
|
quickSort_parallel_internal(x, threadLeft, threadRight, CUTOFF, descending);
|
|
};
|
|
|
|
samediff::Threads::parallel_for(func, 0, numThreads);
|
|
|
|
// Merge sorted segments if we used multiple threads
|
|
if (numThreads > 1) {
|
|
auto xBuff = x->bufferAsT<T>();
|
|
std::vector<T> temp(length);
|
|
for (int size = segmentSize; size < length; size *= 2) {
|
|
for (int left = 0; left < length; left += 2 * size) {
|
|
int mid = std::min(left + size, (int)length);
|
|
int right = std::min(left + 2 * size, (int)length);
|
|
int i = left, j = mid, k = left;
|
|
|
|
// Merge two segments
|
|
while (i < mid && j < right) {
|
|
if (descending) {
|
|
temp[k++] = (xBuff[i] >= xBuff[j]) ? xBuff[i++] : xBuff[j++];
|
|
} else {
|
|
temp[k++] = (xBuff[i] <= xBuff[j]) ? xBuff[i++] : xBuff[j++];
|
|
}
|
|
}
|
|
while (i < mid) temp[k++] = xBuff[i++];
|
|
while (j < right) temp[k++] = xBuff[j++];
|
|
|
|
// Copy back
|
|
for (i = left; i < right; i++) {
|
|
xBuff[i] = temp[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void SpecialMethods<T>::sortTadGeneric(NDArray *input, sd::LongType *dimension, int dimensionLength, bool descending) {
|
|
auto x = input->bufferAsT<T>();
|
|
sd::LongType xLength = input->lengthOf();
|
|
sd::LongType xTadLength = shape::tadLength(input->shapeInfo(), dimension, dimensionLength);
|
|
int numTads = xLength / xTadLength;
|
|
|
|
const std::vector<sd::LongType> dimVector(dimension, dimension + dimensionLength);
|
|
auto pack = sd::ConstantTadHelper::getInstance().tadForDimensions(
|
|
const_cast<sd::LongType *>(input->shapeInfo()), const_cast<sd::LongType *>(dimVector.data()), false);
|
|
|
|
auto func = PRAGMA_THREADS_FOR {
|
|
for (auto r = start; r < stop; r++) {
|
|
NDArray *dx = pack->extractTadView(input, r);
|
|
quickSort_parallel(dx, xTadLength, descending);
|
|
delete dx;
|
|
}
|
|
};
|
|
samediff::Threads::parallel_tad(func, 0, numTads);
|
|
}
|
|
|
|
} // namespace sd
|