Files
2026-07-13 12:47:05 +08:00

752 lines
28 KiB
Plaintext

/* ******************************************************************************
*
*
* 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
// @author Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/ConstantShapeHelper.h>
#include <helpers/ConstantTadHelper.h>
#include <helpers/PointersManager.h>
#include <helpers/ShapeUtils.h>
#include <ops/declarable/helpers/scatter.h>
#include <numeric>
#include "execution/cuda/LaunchDims.h"
#include "helpers/DebugHelper.h"
namespace sd {
namespace ops {
namespace helpers {
///////////////////////////////////////////////////////////////////
// x - indices, y - contains number of bad indices, z - input/output
template <typename X>
SD_KERNEL static void checkIndicesCuda(const void *vx, const LongType *xShapeInfo, LongType *y,
const LongType *zShapeInfo, const int axis) {
const auto x = reinterpret_cast<const X *>(vx);
__shared__ LongType xRank, xLen, numOfBadIndxPerBlock;
__shared__ const LongType *xShape, *xStride, *zShape;
__shared__ LongType *coords;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType *>(shmem);
xRank = shape::rank(xShapeInfo);
xLen = shape::length(xShapeInfo);
xShape = shape::shapeOf(xShapeInfo);
xStride = shape::stride(xShapeInfo);
zShape = shape::shapeOf(zShapeInfo);
numOfBadIndxPerBlock = 0;
}
__syncthreads();
auto xCoords = coords + threadIdx.x * xRank;
for (LongType i = blockIdx.x * blockDim.x + threadIdx.x; i < xLen; i += gridDim.x * blockDim.x) {
INDEX2COORDS(i, xRank, xShape, xCoords);
LongType xOffset;
COORDS2INDEX(xRank, xStride, xCoords, xOffset);
const LongType currentInd = x[xOffset];
const LongType limit = shape::sizeAt(zShapeInfo, axis == -1 ? xCoords[xRank - 1] : axis);
if (currentInd >= limit) {
sd::math::atomics::sd_atomicAdd<LongType>(&numOfBadIndxPerBlock, 1);
}
}
__syncthreads();
if (threadIdx.x == 0 && numOfBadIndxPerBlock != 0) {
sd::math::atomics::sd_atomicAdd<LongType>(y, numOfBadIndxPerBlock);
}
}
///////////////////////////////////////////////////////////////////
template <typename X>
static void checkIndicesCudaLauncher(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
const cudaStream_t *stream, const void *vx, const LongType *xShapeInfo,
LongType *y, const LongType *zShapeInfo, const int axis) {
checkIndicesCuda<X><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(vx, xShapeInfo, y, zShapeInfo, axis);
sd::DebugHelper::checkErrorCode(const_cast<cudaStream_t *>(stream), "checkIndicesCuda failed");
}
///////////////////////////////////////////////////////////////////
LongType checkIndices(LaunchContext *context, NDArray&indices, NDArray&output, const int axis) {
const int threadsPerBlock = SD_MAX_NUM_THREADS / 2;
const int blocksPerGrid = (indices.lengthOf() + threadsPerBlock - 1) / threadsPerBlock;
const int sharedMem = threadsPerBlock * sizeof(LongType) * indices.rankOf() + 256;
dim3 scatterDimsIndices = scatterDimsCheckIndices(indices.lengthOf(), indices.rankOf());
const auto xType = indices.dataType();
PointersManager manager(context, "scatterNDcheckIndices");
// scalar, initial value = 0
NDArray numOfBadIndx(INT64, context, true);
NDArray::prepareSpecialUse({&numOfBadIndx}, {&indices});
BUILD_SINGLE_SELECTOR(
xType, checkIndicesCudaLauncher,
(scatterDimsIndices.y, scatterDimsIndices.x, scatterDimsIndices.z, context->getCudaStream(),
indices.specialBuffer(), indices.specialShapeInfo(),
reinterpret_cast<sd::LongType *>(numOfBadIndx.specialBuffer()), output.specialShapeInfo(), axis),
SD_INTEGER_TYPES);
NDArray::registerSpecialUse({&numOfBadIndx}, {&indices});
manager.synchronize();
return numOfBadIndx.t<LongType>(0);
}
///////////////////////////////////////////////////////////////////
// x - indices, y - updates, z - input/output
template <typename X, typename Y>
SD_KERNEL static void scatterLockCuda(const int opCode, const void *vx, const LongType *xShapeInfo, const void *vy,
const LongType *yShapeInfo, void *vz, const LongType *zShapeInfo) {
const auto x = reinterpret_cast<const X *>(vx);
const auto y = reinterpret_cast<const Y *>(vy);
auto z = reinterpret_cast<Y *>(vz);
__shared__ LongType xRank, yRank, zRank, xNonUnitDim, yNonUnitDim, zNonUnitDim;
__shared__ const LongType *xShape, *yShape, *zShape, *xStride, *yStride, *zStride;
__shared__ LongType xLen, zLen;
__shared__ bool is1Dcase, xySameStride;
__shared__ LongType *coords;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType *>(shmem);
xRank = shape::rank(xShapeInfo);
yRank = shape::rank(yShapeInfo);
zRank = shape::rank(zShapeInfo);
xShape = shape::shapeOf(xShapeInfo);
yShape = shape::shapeOf(yShapeInfo);
zShape = shape::shapeOf(zShapeInfo);
xStride = shape::stride(xShapeInfo);
yStride = shape::stride(yShapeInfo);
zStride = shape::stride(zShapeInfo);
xLen = shape::length(xShapeInfo);
zLen = shape::length(zShapeInfo);
xNonUnitDim = yNonUnitDim = zNonUnitDim = 0;
is1Dcase = (shape::isCommonVector(zShapeInfo, zNonUnitDim) || shape::isScalar(zShapeInfo)) &&
(shape::isCommonVector(yShapeInfo, yNonUnitDim) || shape::isScalar(yShapeInfo)) &&
(shape::isCommonVector(xShapeInfo, xNonUnitDim) || shape::isScalar(xShapeInfo));
if (is1Dcase) xySameStride = xStride[xNonUnitDim] == yStride[yNonUnitDim];
}
__syncthreads();
LongType yOffset, zOffset;
LongType zFirstCoord, *yCoords, *zCoords;
for (LongType i = blockIdx.x * blockDim.x + threadIdx.x; i < zLen; i += gridDim.x * blockDim.x) {
if (!is1Dcase) {
yCoords = coords + threadIdx.x * (yRank + zRank);
zCoords = yCoords + yRank;
INDEX2COORDS(i, zRank, zShape, zCoords);
}
for (LongType j = 0; j < xLen; ++j) {
if (is1Dcase) {
yOffset = j * yStride[yNonUnitDim];
zFirstCoord = x[xySameStride ? yOffset : j];
if (i != zFirstCoord) continue;
zOffset = i * zStride[zNonUnitDim];
} else {
INDEX2COORDS(j, xRank, xShape, yCoords);
LongType xOffset;
COORDS2INDEX(xRank, xStride, yCoords, xOffset);
zFirstCoord = x[xOffset];
if (zCoords[0] != zFirstCoord) continue;
for (LongType k = 0; k < yRank - xRank; ++k) yCoords[xRank + k] = zCoords[k + 1];
COORDS2INDEX(yRank, yStride, yCoords, yOffset);
COORDS2INDEX(zRank, zStride, zCoords, zOffset);
}
switch (opCode) {
case pairwise::Add:
z[zOffset] += y[yOffset];
break;
case pairwise::Subtract:
z[zOffset] -= y[yOffset];
break;
case pairwise::Multiply:
z[zOffset] *= y[yOffset];
break;
case pairwise::Divide:
z[zOffset] /= y[yOffset];
break;
case pairwise::ReverseSubtract:
z[zOffset] = y[yOffset] - z[zOffset];
break;
case pairwise::ReverseDivide:
z[zOffset] = y[yOffset] / z[zOffset];
break;
case pairwise::CopyPws:
z[zOffset] = y[yOffset];
break;
case pairwise::MaxPairwise:
if (z[zOffset] < y[yOffset]) z[zOffset] = y[yOffset];
break;
case pairwise::MinPairwise:
if (z[zOffset] > y[yOffset]) z[zOffset] = y[yOffset];
break;
default:
continue;
}
}
}
}
///////////////////////////////////////////////////////////////////
// x - indices, y - updates, z - input/output
template <typename X, typename Y>
SD_KERNEL static void scatterCuda(const int opCode, const void *vx, const LongType *xShapeInfo, const void *vy,
const LongType *yShapeInfo, void *vz, const LongType *zShapeInfo) {
const auto x = reinterpret_cast<const X *>(vx);
const auto y = reinterpret_cast<const Y *>(vy);
auto z = reinterpret_cast<Y *>(vz);
__shared__ LongType xRank, yRank, zRank, xNonUnitDim, yNonUnitDim, zNonUnitDim;
__shared__ const LongType *xShape, *yShape, *zShape, *xStride, *yStride, *zStride;
__shared__ LongType yLen;
__shared__ bool is1Dcase, xySameStride;
__shared__ LongType *coords;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType *>(shmem);
xRank = shape::rank(xShapeInfo);
yRank = shape::rank(yShapeInfo);
zRank = shape::rank(zShapeInfo);
xShape = shape::shapeOf(xShapeInfo);
yShape = shape::shapeOf(yShapeInfo);
zShape = shape::shapeOf(zShapeInfo);
xStride = shape::stride(xShapeInfo);
yStride = shape::stride(yShapeInfo);
zStride = shape::stride(zShapeInfo);
yLen = shape::length(yShapeInfo);
xNonUnitDim = yNonUnitDim = zNonUnitDim = 0;
is1Dcase = (shape::isCommonVector(zShapeInfo, zNonUnitDim) || shape::isScalar(zShapeInfo)) &&
(shape::isCommonVector(yShapeInfo, yNonUnitDim) || shape::isScalar(yShapeInfo)) &&
(shape::isCommonVector(xShapeInfo, xNonUnitDim) || shape::isScalar(xShapeInfo));
if (is1Dcase) xySameStride = xStride[xNonUnitDim] == yStride[yNonUnitDim];
}
__syncthreads();
LongType xOffset, yOffset, zOffset;
LongType *yCoords, *zCoords;
if (!is1Dcase) {
yCoords = coords + threadIdx.x * (yRank + zRank);
zCoords = yCoords + yRank;
}
for (LongType i = blockIdx.x * blockDim.x + threadIdx.x; i < yLen; i += gridDim.x * blockDim.x) {
if (is1Dcase) {
yOffset = i * yStride[yNonUnitDim];
zOffset = x[xySameStride ? yOffset : i * xStride[xNonUnitDim]] * zStride[zNonUnitDim];
} else {
INDEX2COORDS(i, yRank, yShape, yCoords);
COORDS2INDEX(yRank, yStride, yCoords, yOffset);
COORDS2INDEX(xRank, xStride, yCoords, xOffset);
zCoords[0] = x[xOffset];
for (LongType j = 0; j < yRank - xRank; ++j) {
zCoords[j + 1] = yCoords[xRank + j];
}
COORDS2INDEX(zRank, zStride, zCoords, zOffset);
}
switch (opCode) {
case pairwise::Add:
z[zOffset] += y[yOffset];
break;
case pairwise::Subtract:
z[zOffset] -= y[yOffset];
break;
case pairwise::Multiply:
z[zOffset] *= y[yOffset];
break;
case pairwise::Divide:
z[zOffset] /= y[yOffset];
break;
case pairwise::ReverseSubtract:
z[zOffset] = y[yOffset] - z[zOffset];
break;
case pairwise::ReverseDivide:
z[zOffset] = y[yOffset] / z[zOffset];
break;
case pairwise::CopyPws:
z[zOffset] = y[yOffset];
break;
case pairwise::MaxPairwise:
if (z[zOffset] < y[yOffset]) z[zOffset] = y[yOffset];
break;
case pairwise::MinPairwise:
if (z[zOffset] > y[yOffset]) z[zOffset] = y[yOffset];
break;
default:
continue;
}
}
}
///////////////////////////////////////////////////////////////////
template <typename X, typename Y>
static void scatterCudaLauncher(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
const cudaStream_t *stream, const int opCode, const void *vx,
const LongType *xShapeInfo, const void *vy, const LongType *yShapeInfo, void *vz,
const LongType *zShapeInfo, const bool lock) {
if (lock)
scatterLockCuda<X, Y><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(opCode, vx, xShapeInfo, vy,
yShapeInfo, vz, zShapeInfo);
else
scatterCuda<X, Y><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(opCode, vx, xShapeInfo, vy, yShapeInfo,
vz, zShapeInfo);
sd::DebugHelper::checkErrorCode(const_cast<cudaStream_t *>(stream), "scatterLockCuda failed");
}
///////////////////////////////////////////////////////////////////
void scatter(LaunchContext *context, pairwise::Ops op, NDArray&indices, NDArray&updates, NDArray &output,
const bool lock) {
const auto xType = indices.dataType();
const auto yType = updates.dataType();
dim3 launchDims = scatterDims(lock ? output.lengthOf() : updates.lengthOf(), updates.rankOf() + output.rankOf());
PointersManager manager(context, "scatter");
NDArray::prepareSpecialUse({&output}, {&updates, &indices});
BUILD_DOUBLE_SELECTOR(xType, yType, scatterCudaLauncher,
(launchDims.y, launchDims.x, launchDims.z, context->getCudaStream(), op,
indices.specialBuffer(), indices.specialShapeInfo(), updates.specialBuffer(),
updates.specialShapeInfo(), output.specialBuffer(), output.specialShapeInfo(), lock),
SD_INDEXING_TYPES, SD_GENERIC_NUMERIC_TYPES);
NDArray::registerSpecialUse({&output}, {&updates, &indices});
manager.synchronize();
}
///////////////////////////////////////////////////////////////////
// x - indices, y - updates, z - output
template <typename X, typename Y>
SD_KERNEL static void scatterNDLockCuda(const int opCode, const void *vx, const LongType *xShapeInfo, const void *vy,
const LongType *yShapeInfo, void *vz, const LongType *zShapeInfo) {
const auto x = reinterpret_cast<const X *>(vx);
const auto y = reinterpret_cast<const Y *>(vy);
auto z = reinterpret_cast<Y *>(vz);
__shared__ LongType xRank, yRank, zRank, biggerXYRank, xLastDim, xNonUnitDim, yNonUnitDim, zNonUnitDim;
__shared__ const LongType *xShape, *yShape, *zShape, *xStride, *yStride, *zStride;
__shared__ LongType zLen, len;
__shared__ bool is1Dcase;
__shared__ LongType *coords;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType *>(shmem);
xRank = shape::rank(xShapeInfo);
yRank = shape::rank(yShapeInfo);
zRank = shape::rank(zShapeInfo);
xLastDim = shape::sizeAt(xShapeInfo, -1);
xShape = shape::shapeOf(xShapeInfo);
yShape = shape::shapeOf(yShapeInfo);
zShape = shape::shapeOf(zShapeInfo);
xStride = shape::stride(xShapeInfo);
yStride = shape::stride(yShapeInfo);
zStride = shape::stride(zShapeInfo);
biggerXYRank = xRank > yRank ? xRank : yRank;
xNonUnitDim = yNonUnitDim = zNonUnitDim = 0;
is1Dcase = (shape::isCommonVector(zShapeInfo, zNonUnitDim) || shape::isScalar(zShapeInfo)) &&
(shape::isCommonVector(yShapeInfo, yNonUnitDim) || shape::isScalar(yShapeInfo)) &&
(shape::isCommonVector(xShapeInfo, xNonUnitDim) || shape::isScalar(xShapeInfo));
len = is1Dcase ? shape::length(xShapeInfo) : shape::length(xShapeInfo) / xLastDim;
zLen = shape::length(zShapeInfo);
}
__syncthreads();
LongType yOffset, zOffset, xOffset;
LongType *yCoords, *zCoords;
if (!is1Dcase) {
yCoords = coords + threadIdx.x * (biggerXYRank + zRank);
zCoords = yCoords + biggerXYRank;
}
for (LongType i = blockIdx.x * blockDim.x + threadIdx.x; i < zLen; i += gridDim.x * blockDim.x) {
if (!is1Dcase) INDEX2COORDS(i, zRank, zShape, zCoords);
for (LongType j = 0; j < len; j++) {
if (is1Dcase) {
if (x[j * xStride[xNonUnitDim]] != i) continue;
COORDS2INDEX(yRank, yStride, yCoords, yOffset);
COORDS2INDEX(zRank, zStride, zCoords, zOffset);
} else {
INDEX2COORDS(j, xRank - 1, xShape, yCoords);
yCoords[xRank - 1] = 0;
COORDS2INDEX(xRank, xStride, yCoords, xOffset);
if (zCoords[0] != x[xOffset]) continue;
bool matched = true;
for (LongType k = 1; k < xLastDim; k++) {
yCoords[xRank - 1] = k;
COORDS2INDEX(xRank, xStride, yCoords, xOffset);
if (zCoords[k] != x[xOffset]) {
matched = false;
break;
}
}
if (!matched) continue;
for (LongType k = xLastDim; k < zRank; ++k) yCoords[yRank - zRank + k] = zCoords[k];
COORDS2INDEX(yRank, yStride, yCoords, yOffset);
COORDS2INDEX(zRank, zStride, zCoords, zOffset);
}
switch (opCode) {
case pairwise::Add:
z[zOffset] += y[yOffset];
break;
case pairwise::Subtract:
z[zOffset] -= y[yOffset];
break;
case pairwise::Multiply:
z[zOffset] *= y[yOffset];
break;
case pairwise::Divide:
z[zOffset] /= y[yOffset];
break;
case pairwise::ReverseSubtract:
z[zOffset] = y[yOffset] - z[zOffset];
break;
case pairwise::ReverseDivide:
z[zOffset] = y[yOffset] / z[zOffset];
break;
case pairwise::CopyPws:
z[zOffset] = y[yOffset];
break;
case pairwise::MaxPairwise:
if (z[zOffset] < y[yOffset]) z[zOffset] = y[yOffset];
break;
case pairwise::MinPairwise:
if (z[zOffset] > y[yOffset]) z[zOffset] = y[yOffset];
break;
default:
continue;
}
}
}
}
///////////////////////////////////////////////////////////////////
// x - indices, y - updates, z - output
template <typename X, typename Y>
SD_KERNEL static void scatterNDCuda(const int opCode, const void* vx, const LongType* xShapeInfo, const void* vy,
const LongType* yShapeInfo, void* vz, const LongType* zShapeInfo) {
// Cast input and output pointers
const auto x = reinterpret_cast<const X*>(vx);
const auto y = reinterpret_cast<const Y*>(vy);
auto z = reinterpret_cast<Y*>(vz);
// Shared memory for shape information and flags
__shared__ LongType xRank, yRank, zRank, biggerXYRank, xLastDim, xNonUnitDim, yNonUnitDim, zNonUnitDim, yLen;
__shared__ bool is1Dcase;
// Shared memory for coordinates
__shared__ LongType* coords;
if (threadIdx.x == 0) {
// Dynamically allocated shared memory
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType*>(shmem);
// Initialize shared values
xRank = shape::rank(xShapeInfo);
yRank = shape::rank(yShapeInfo);
zRank = shape::rank(zShapeInfo);
xLastDim = shape::sizeAt(xShapeInfo, -1);
yLen = shape::length(yShapeInfo);
biggerXYRank = max(xRank, yRank);
xNonUnitDim = yNonUnitDim = zNonUnitDim = 0;
// Check if the operation involves 1D cases
is1Dcase = (shape::isCommonVector(zShapeInfo, zNonUnitDim) || shape::isScalar(zShapeInfo)) &&
(shape::isCommonVector(yShapeInfo, yNonUnitDim) || shape::isScalar(yShapeInfo)) &&
(shape::isCommonVector(xShapeInfo, xNonUnitDim) || shape::isScalar(xShapeInfo));
}
__syncthreads();
// Dynamically allocated memory for local coordinates
LongType* yCoords = coords + threadIdx.x * (biggerXYRank + zRank);
LongType* zCoords = yCoords + biggerXYRank;
// Process each element in y
for (LongType i = blockIdx.x * blockDim.x + threadIdx.x; i < yLen; i += gridDim.x * blockDim.x) {
LongType yOffset, zOffset;
// Convert linear index to multi-dimensional coordinates for y
INDEX2COORDS(i, yRank, shape::shapeOf(yShapeInfo), yCoords);
COORDS2INDEX(yRank, shape::stride(yShapeInfo), yCoords, yOffset);
// Save the last coordinate of y if needed
if (yRank >= xRank) {
zCoords[xLastDim] = yCoords[xRank - 1];
}
// Map y coordinates to x and z coordinates
for (LongType j = 0; j < xLastDim; ++j) {
yCoords[xRank - 1] = j;
COORDS2INDEX(xRank, shape::stride(xShapeInfo), yCoords, zCoords[j]);
}
// Adjust remaining coordinates for z
for (LongType j = xLastDim + 1; j < zRank; ++j) {
zCoords[j] = yCoords[yRank - zRank + j];
}
// Compute linear index for z
COORDS2INDEX(zRank, shape::stride(zShapeInfo), zCoords, zOffset);
// Perform the operation based on opCode
switch (opCode) {
case pairwise::Add:
z[zOffset] += y[yOffset];
break;
case pairwise::Subtract:
z[zOffset] -= y[yOffset];
break;
case pairwise::Multiply:
z[zOffset] *= y[yOffset];
break;
case pairwise::Divide:
z[zOffset] /= y[yOffset];
break;
case pairwise::ReverseSubtract:
z[zOffset] = y[yOffset] - z[zOffset];
break;
case pairwise::ReverseDivide:
z[zOffset] = y[yOffset] / z[zOffset];
break;
case pairwise::CopyPws:
z[zOffset] = y[yOffset];
break;
case pairwise::MaxPairwise:
z[zOffset] = max(z[zOffset], y[yOffset]);
break;
case pairwise::MinPairwise:
z[zOffset] = min(z[zOffset], y[yOffset]);
break;
default:
break;
}
}
}
///////////////////////////////////////////////////////////////////
template <typename X, typename Y>
static void scatterNDCudaLauncher(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
const cudaStream_t *stream, const int opCode, const void *vx,
const LongType *xShapeInfo, const void *vy, const LongType *yShapeInfo, void *vz,
const LongType *zShapeInfo, const bool lock) {
if (lock)
scatterNDLockCuda<X, Y><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(opCode, vx, xShapeInfo, vy,
yShapeInfo, vz, zShapeInfo);
else
scatterNDCuda<X, Y><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(opCode, vx, xShapeInfo, vy, yShapeInfo,
vz, zShapeInfo);
sd::DebugHelper::checkErrorCode(const_cast<cudaStream_t *>(stream), "scatterNDCuda failed");
}
///////////////////////////////////////////////////////////////////
void scatterND(LaunchContext *context, pairwise::Ops op, NDArray&indices, NDArray&updates,
NDArray &output, const bool lock) {
const int xRank = indices.rankOf();
const int yRank = updates.rankOf();
const int zRank = output.rankOf();
dim3 launchDims =
scatterNdDims(lock ? output.lengthOf() : updates.lengthOf(), ((yRank > xRank ? yRank : xRank) + zRank));
const auto xType = indices.dataType();
const auto yType = updates.dataType();
PointersManager manager(context, "scatterND");
NDArray::prepareSpecialUse({&output}, {&updates, &indices});
BUILD_DOUBLE_SELECTOR(xType, yType, scatterNDCudaLauncher,
(launchDims.y, launchDims.x, launchDims.z, context->getCudaStream(), op,
indices.specialBuffer(), indices.specialShapeInfo(), updates.specialBuffer(),
updates.specialShapeInfo(), output.specialBuffer(), output.specialShapeInfo(), lock),
SD_INDEXING_TYPES, SD_GENERIC_NUMERIC_TYPES);
NDArray::registerSpecialUse({&output}, {&updates, &indices});
manager.synchronize();
}
///////////////////////////////////////////////////////////////////
template <typename X, typename Z>
SD_KERNEL void scatterForLossCuda(const void* vx, const LongType* xShapeInfo, void* vy, const LongType* yShapeInfo,
void* vz, const LongType* zShapeInfo) {
// Cast input and output pointers
const auto x = reinterpret_cast<const X*>(vx);
auto y = reinterpret_cast<Z*>(vy);
auto z = reinterpret_cast<Z*>(vz);
// Shared memory for shape information and coordinates
__shared__ LongType xLen;
__shared__ LongType xRank;
__shared__ const LongType* xShape;
__shared__ const LongType* xStride;
__shared__ const LongType* yStride;
__shared__ const LongType* zStride;
if (threadIdx.x == 0) {
// Initialize shared memory variables
xLen = shape::length(xShapeInfo);
xRank = shape::rank(xShapeInfo);
xShape = shape::shapeOf(xShapeInfo);
xStride = shape::stride(xShapeInfo);
yStride = shape::stride(yShapeInfo);
zStride = zShapeInfo ? shape::stride(zShapeInfo) : nullptr;
}
__syncthreads();
// Calculate global thread index
const LongType xInd = threadIdx.x + blockIdx.x * blockDim.x;
// Return if the thread index exceeds the length of x
if (xInd >= xLen) return;
// Dynamically allocated shared memory for coordinates
extern __shared__ unsigned char shmem[];
auto coords = reinterpret_cast<LongType*>(shmem) + threadIdx.x * (xRank + 1);
// Convert linear index to coordinates for x
INDEX2COORDS(xInd, xRank, xShape, coords);
// Calculate offset for x
LongType xOffset;
COORDS2INDEX(xRank, xStride, coords, xOffset);
// Update the last coordinate with the value from x
coords[xRank] = x[xOffset];
// Calculate offset for y
LongType yOffset;
COORDS2INDEX(xRank + 1, yStride, coords, yOffset);
if (z == nullptr) {
// Gradient calculation
y[yOffset] -= 1.f;
} else {
// Calculate offset for z
LongType zOffset;
COORDS2INDEX(xRank + 1, zStride, coords, zOffset);
// Update z with the value from y
z[zOffset] = y[yOffset];
}
}
///////////////////////////////////////////////////////////////////
template <typename X, typename Z>
static void scatterForLossCudaLauncher(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
const cudaStream_t *stream, const void *vx, const LongType *xShapeInfo, void *vy,
const LongType *yShapeInfo, void *vz, const LongType *zShapeInfo) {
scatterForLossCuda<X, Z>
<<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(vx, xShapeInfo, vy, yShapeInfo, vz, zShapeInfo);
sd::DebugHelper::checkErrorCode(const_cast<cudaStream_t *>(stream), "scatterUpdateCuda failed");
}
///////////////////////////////////////////////////////////////////
void scatterForLoss(LaunchContext *context, NDArray&indices, NDArray &updates, NDArray &output,
const bool calcGrad) {
// shapes of indices and output must be the same
// shape of indices should be the same as updates shape with last dimension excluded, for example if updates is
// {a,b,c} then indices should be {a,b}
PointersManager manager(context, "scatterForLoss");
dim3 launchDIms = scatterDims(indices.lengthOf(), updates.rankOf());
if (calcGrad) {
NDArray::prepareSpecialUse({&updates}, {&indices});
BUILD_DOUBLE_SELECTOR(
indices.dataType(), updates.dataType(), scatterForLossCudaLauncher,
(launchDIms.y, launchDIms.x, launchDIms.z, context->getCudaStream(), indices.specialBuffer(),
indices.specialShapeInfo(), updates.specialBuffer(), updates.specialShapeInfo(), nullptr, nullptr),
SD_INDEXING_TYPES, SD_FLOAT_TYPES);
NDArray::registerSpecialUse({&updates}, {&indices});
} else {
NDArray::prepareSpecialUse({&output}, {&indices, &updates});
BUILD_DOUBLE_SELECTOR(indices.dataType(), updates.dataType(), scatterForLossCudaLauncher,
(launchDIms.y, launchDIms.x, launchDIms.z, context->getCudaStream(), indices.specialBuffer(),
indices.specialShapeInfo(), updates.specialBuffer(), updates.specialShapeInfo(),
output.specialBuffer(), output.specialShapeInfo()),
SD_INDEXING_TYPES, SD_FLOAT_TYPES);
NDArray::registerSpecialUse({&output}, {&indices, &updates});
}
manager.synchronize();
}
} // namespace helpers
} // namespace ops
} // namespace sd