chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:05 +08:00
commit 4f3b7da785
7394 changed files with 2005594 additions and 0 deletions
@@ -0,0 +1,53 @@
/* ******************************************************************************
*
*
* 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
//
#include <helpers/ArrayUtils.h>
namespace sd {
namespace ArrayUtils {
void toIntPtr(std::initializer_list<int> list, int* target) {
std::vector<int> vec(list);
toIntPtr(vec, target);
}
void toIntPtr(std::vector<int>& list, int* target) { memcpy(target, list.data(), list.size() * sizeof(int)); }
void toLongPtr(std::initializer_list<LongType> list, LongType* target) {
std::vector<LongType> vec(list);
toLongPtr(vec, target);
}
void toLongPtr(std::vector<LongType>& list, LongType* target) {
memcpy(target, list.data(), list.size() * sizeof(LongType));
}
std::vector<LongType> toLongVector(std::vector<int> vec) {
std::vector<LongType> result(vec.size());
LongType vecSize = vec.size();
for (LongType e = 0; e < vecSize; e++) result[e] = vec[e];
return result;
}
std::vector<LongType> toLongVector(std::vector<LongType> vec) { return vec; }
} // namespace ArrayUtils
} // namespace sd
@@ -0,0 +1,513 @@
/*
* ******************************************************************************
* *
* *
* * 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 Paul Dubs
// @author Adam Gibson
//
#ifndef LIBND4J_ATTENTIONHELPER_CPP
#define LIBND4J_ATTENTIONHELPER_CPP
#include "../AttentionHelper.h"
#include <indexing/NDIndexUtils.h>
#include <helpers/AttentionHelper.h>
#include <ops/declarable/CustomOperations.h>
#include <ops/declarable/helpers/batched_gemm.h>
#if NOT_EXCLUDED(OP_multi_head_dot_product_attention)
namespace sd {
NDArray AttentionHelper::multiHeadProject(NDArray *input, NDArray *projectionMatrix,
LaunchContext *context) {
auto miniBatchSize = input->sizeAt(0);
auto seqLength = input->sizeAt(2);
auto numHeads = projectionMatrix->sizeAt(0);
auto projectedSize = projectionMatrix->sizeAt(1);
std::vector<sd::LongType> epsPermVec = {1, 0,2};
auto inputPerm = input->permute(epsPermVec, false, false); //[batch, nIn, timeSteps] -> [nIn, batch, timeSteps]
std::vector<sd::LongType> inputPermShape = {input->sizeAt(1), (miniBatchSize * seqLength)};
auto inputPrep = inputPerm->reshape('c', inputPermShape); //[nIn, batch*timeSteps]
std::vector<sd::LongType> projectionMatrixShape = {numHeads * projectionMatrix->sizeAt(1), projectionMatrix->sizeAt(2)};
auto projectionPrep = projectionMatrix->reshape(
'c',
projectionMatrixShape); //[nHeads, hS, nIn] -> [nHeads*hS, nIn]
std::vector<LongType> projectedShape = {numHeads * projectionMatrix->sizeAt(1), (miniBatchSize * seqLength)};
NDArray projected('c',projectedShape, input->dataType(),
context); //[nHeads*hS, batch*timeSteps]
ops::matmul mmul;
mmul.execute({&projectionPrep, &inputPrep}, {&projected});
projected.reshapei({numHeads, projectedSize, miniBatchSize, seqLength});
projected.permutei({2, 0, 1, 3}, false, false); //[minibatch, numHeads, projectedSize, seqLength]
return projected;
}
/**
* @param shape
* @return
*/
NDArray * AttentionHelper::lowerTriangularMask(std::vector<LongType> *shape) {
auto rowIndexOnes = NDArrayFactory::valueOf(*shape,1,'c');
auto colIndexOnes = NDArrayFactory::valueOf(*shape, 1, 'c');
ops::cumsum cumsum;
auto rowCumSum = cumsum.evaluate({rowIndexOnes},{},{-2,0},{});
auto colsCumSum = cumsum.evaluate({colIndexOnes}, {}, {-1, 0}, {});
ops::greater_equal greaterEqual;
auto ret = greaterEqual.evaluate({rowCumSum.at(0),colsCumSum.at(0)});
return ret[0];
}
/**
* @param query
* @param value
* @return
*/
NDArray *AttentionHelper::computeCasualMask(NDArray *query, NDArray *value, bool multiHead) {
if(multiHead) {
auto qSeqLength = query->sizeAt(1);
auto vSeqLength = value != nullptr ? value->sizeAt(1) : qSeqLength;
ops::matrix_band_part matrixBandPart;
auto ones = NDArrayFactory::create('c',{1,qSeqLength,vSeqLength}, INT32);
int assignVal = 1;
ones->assign(assignVal);
auto lower = matrixBandPart.evaluate({ones},{},{-1,0});
auto ret = lower.at(0)->cast(BOOL);
delete ones;
return ret;
} else {
std::vector<LongType> causalMaskShape2;
causalMaskShape2.push_back(query->sizeAt(0));
//4d
if(query->rankOf() > 3)
causalMaskShape2.push_back(query->sizeAt(1));
causalMaskShape2.push_back(query->sizeAt(-2));
causalMaskShape2.push_back(value->sizeAt(-2));
auto ret = lowerTriangularMask(&causalMaskShape2);
return ret;
}
}
/**
* @param query
* @param value
* @param attentionMask
* @param useCausalMask
* @return
*/
NDArray *AttentionHelper::computeAttentionMask(NDArray *query, NDArray *value, NDArray *queryMask, NDArray *valueMask,
NDArray *attentionMask, bool useCausalMask) {
auto internalQueryMask = queryMask;
auto internalValueMask = valueMask;
NDArray *autoMask = nullptr;
ops::create_view createView;
ops::boolean_and booleanAnd;
auto all = NDIndexUtils::createAll();
auto newAxis = NDIndexUtils::createNewAxis();
if (internalQueryMask != nullptr && !internalQueryMask->isEmpty()) {
internalQueryMask = queryMask->cast(BOOL);
if (autoMask != nullptr && !autoMask->isEmpty()) {
autoMask = createView.evaluate({internalQueryMask, all, all, newAxis}).at(0);
}
}
if (valueMask != nullptr && !valueMask->isEmpty()) {
internalValueMask = valueMask->cast(BOOL);
auto mask = createView.evaluate({internalValueMask, all, newAxis, all}).at(0);
if (autoMask == nullptr || autoMask->isEmpty()) {
autoMask = mask;
} else {
autoMask = booleanAnd.evaluate({autoMask, mask}).at(0);
}
}
if (useCausalMask) {
auto mask = computeCasualMask(query, value, false);
if (autoMask == nullptr) {
autoMask = mask;
} else {
autoMask = booleanAnd.evaluate({autoMask, mask}).at(0);
}
}
if (autoMask != nullptr && !autoMask->isEmpty()) {
if (attentionMask == nullptr || attentionMask->isEmpty()) {
return autoMask;
} else {
auto ret = booleanAnd.evaluate({attentionMask, autoMask}).at(0);
return ret;
}
}
delete all;
delete newAxis;
return autoMask;
}
NDArray * AttentionHelper::mergeMasks(NDArray *x, NDArray *y) {
if(x == nullptr || x->isEmpty()) {
return y;
}
if (y == nullptr || y->isEmpty()) {
return x;
}
ops::boolean_and booleanAnd;
auto ret = booleanAnd.evaluate({x,y});
return ret.at(0);
}
void AttentionHelper::applyAttentionScores(NDArray *scores, NDArray *value, NDArray *scoresMask,
double dropout, int randomSeed, NDArray *applyScoresOut, NDArray *attentionLogits,
NDArray *dropoutMask) {
ops::boolean_not booleanNot;
ops::softmax softmax;
ops::dropout dropoutOp;
ops::matmul matmul;
int softmaxDim = -1;
if (scoresMask != nullptr && !scoresMask->isEmpty()) {
REQUIRE_TRUE(scoresMask->sizeAt(-2) == 1 || scoresMask->sizeAt(-2) == scores->sizeAt(-2),0,
"Scores mask must be either broadcastable or equal to scores shape. scores size at -2: was: %i scores size at -2 was: %i",scoresMask->sizeAt(-2),scores->sizeAt(-2));
REQUIRE_TRUE(scoresMask->sizeAt(-1) == scores->sizeAt(-1),0,
"Scores mask must be either broadcastable or equal to scores shape. scores size at -1: was: %i scores size at -1 was: %i",scoresMask->sizeAt(-1),scores->sizeAt(-1));
auto castedScoresMask = scoresMask->cast(BOOL);
auto paddingMask = booleanNot.evaluate({castedScoresMask}).at(0);
auto paddingMaskCast = paddingMask->cast(scores->dataType());
if (attentionLogits->dataType() == BFLOAT16) {
auto minus = 65504 * *paddingMaskCast;
*attentionLogits -= *minus;
delete minus;
} else {
auto minus = 1.0e9 * *paddingMask;
*attentionLogits -= *minus;
delete minus;
}
if(paddingMaskCast != paddingMask) {
delete paddingMaskCast;
}
if(scoresMask != castedScoresMask) {
delete castedScoresMask;
}
}
softmax.execute({attentionLogits},{scores},{},{softmaxDim});
auto weights = scores;
if (dropout > 0) {
dropoutOp.execute({weights},{weights,dropoutMask},{dropout},{randomSeed});
}
//batch size, tq tv
//batch size tv dim
//output: batch size, tq dim
matmul.execute({weights,value},{applyScoresOut});
}
void AttentionHelper::dotProductAttentionBpHelper(NDArray *query, NDArray *key, NDArray *values,
double scale,
NDArray *dLdq, NDArray *dLdk, NDArray *dLdv, NDArray *eps, LongType dropoutSeed, NDArray *qMask, NDArray *vMask, bool useCausalMask, double dropout, bool training,
NDArray *attentionScoresWeights, NDArray *attentionLogits,
NDArray *dropoutMask) {
ops::matmul_bp matMulBp;
ops::softmax_bp softmaxBp;
NDArray dldW(attentionScoresWeights->shapeInfo());
NDArray dldS(attentionScoresWeights->shapeInfo());
NDArray * mask = nullptr;
NDArray *causalPointer = nullptr;
if(useCausalMask) {
std::vector<LongType> causalMaskShape2;
causalMaskShape2.push_back(attentionLogits->sizeAt(0));
//4d
if(attentionLogits->rankOf() > 3)
causalMaskShape2.push_back(attentionLogits->sizeAt(1));
for(int i = attentionLogits->rankOf() - 2; i < attentionLogits->rankOf(); i++) {
causalMaskShape2.push_back(attentionLogits->sizeAt(i));
}
causalPointer = lowerTriangularMask(&causalMaskShape2);
}
mask = mergeMasks(vMask,causalPointer);
matMulBp.execute({attentionScoresWeights,values,eps},{&dldW,dLdv},{},{});
if(dropout > 0.0 && training) {
ops::dropout_bp dropoutOp;
auto inputs = {attentionScoresWeights,dropoutMask,&dldW};
dropoutOp.execute(inputs,{&dldW},{dropout},{dropoutSeed},{false});
}
softmaxBp.execute({attentionLogits,&dldW,attentionScoresWeights},{&dldS},{},{-1},{});
if(scale != 0.0 && scale != 1.0) {
dldS *= scale;
}
// Initialize times as a scalar placeholder (will be reassigned if mask is present)
NDArray times(query->dataType(), query->getContext(), true);
if(mask != nullptr && !mask->isEmpty()) {
ops::expand_dims expandDims;
auto maskCast = mask->cast(query->dataType());
auto mask2 = *maskCast * 1e9;
times = *mask2;
dldS *= times;
delete mask2;
delete maskCast;
}
matMulBp.execute({query,key,&dldS},{dLdq,dLdk},{},{0,1,0});
}
/**
*
* @param query
* @param key
* @param scoreMode
* @param scale
* @return
*/
void AttentionHelper::attentionBpHelper(NDArray *query, NDArray *key, NDArray *values, double scale, NDArray *dLdq,
NDArray *dLdk, NDArray *dLdv, NDArray *eps,
LongType dropoutSeed,
NDArray *qMask, NDArray *vMask,
bool useCausalMask, double dropout, bool training, NDArray *attentionScoresOut,
NDArray *attentionScoresWeights,
NDArray *attentionScoresLogits,
NDArray *dropoutMask) {
dotProductAttentionBpHelper(query, key, values, scale, dLdq, dLdk, dLdv, eps, dropoutSeed, qMask, vMask,
useCausalMask, dropout, training, attentionScoresWeights, attentionScoresLogits,
dropoutMask);
}
/**
*
* @param query
* @param key
* @param scoreMode
* @param scale
* @return
*/
void AttentionHelper::attentionHelper(NDArray *query, NDArray *key, double scale, NDArray *attentionLogits) {
ops::matmul matmul3;
matmul3.execute({query,key},{attentionLogits},{},{0,1});
if(scale != 0.0 && scale != 1.0) {
*attentionLogits *= scale;
}
// Clamp attention logits to prevent numerical overflow in subsequent softmax
// Values beyond this range would produce Inf in exp() which leads to NaN
// Use clipbyvalue op for proper clamping
ops::clipbyvalue clipOp;
clipOp.execute({attentionLogits}, {attentionLogits}, {-1e4, 1e4}, {});
}
/**
* @param inputs
* @param mask
* @param training
* @param returnAttentionScores
* @param useCausalMask
*/
void AttentionHelper::doAttentionBp(std::vector<NDArray *> &inputs, std::vector<NDArray *> &masks, bool training,
bool useCausalMask, double dropout, double scale, std::vector<NDArray *> outputs,
LongType dropoutSeed) {
auto q = inputs[0];
auto v = inputs[1];
auto k = inputs[2];
auto attentionScoresOut = inputs[3];
auto attentionScoresWeights = inputs[4];
auto attentionScoresLogits = inputs[5];
auto eps = inputs[6];
auto dropoutMask = inputs.size() > 7 ? inputs[7] : inputs[7];
ops::expand_dims expandDims;
ops::ones_as onesAs;
ops::shape_of shapeOf;
ops::concat concatOp;
ops::create_view createView;
auto qMask = masks.size() > 0 ? masks[0] : nullptr;
auto vMask = masks.size() > 1 ? masks[1] : nullptr;
auto vmaskInternal = vMask;
auto qMaskInternal = qMask;
if(vMask != nullptr && !vMask->isEmpty() && vMask->rankOf() < v->rankOf()) {
vmaskInternal = expandDims.evaluate({vMask},{},{-2}).at(0);
}
if(qMask != nullptr && !qMask->isEmpty()) {
qMaskInternal = expandDims.evaluate({qMaskInternal},{},{-1}).at(0);
}
auto dLdq = outputs[0];
auto dLdv = outputs[1];
auto dLdk = outputs[2];
attentionBpHelper(q, k, v, scale, dLdq, dLdk, dLdv, eps, dropoutSeed, qMaskInternal, vmaskInternal, useCausalMask,
dropout, training, attentionScoresOut, attentionScoresWeights, attentionScoresLogits, dropoutMask);
}
/**
* @param inputs
* @param mask
* @param training
* @param returnAttentionScores
* @param useCausalMask
*/
void AttentionHelper::doAttention(std::vector<NDArray *> &inputs, std::vector<NDArray *> &masks, bool training,
bool useCausalMask, double dropout, double scale, NDArray *attentionScores,
int dropoutSeed, NDArray *applyScoresOut, NDArray *attentionLogits,
NDArray *dropoutMask) {
auto q = inputs[0];
auto v = inputs[1];
auto k = inputs.size() > 2 ? inputs[2] : v;
auto concatWeights = inputs.size() > 3 ? inputs[3] : nullptr;
ops::expand_dims expandDims;
ops::ones_as onesAs;
ops::shape_of shapeOf;
ops::concat concatOp;
ops::create_view createView;
auto qMask = masks.size() > 0 ? masks[0] : nullptr;
auto vMask = masks.size() > 1 ? masks[1] : nullptr;
auto vmaskInternal = vMask;
auto qMaskInternal = qMask;
NDArray *casualPointer = nullptr;
//inputs: query and value
//shape: batch_size Tq dim (batch_size Tv dim)
//note this does not apply softmax yet, we are just computing logits here
attentionHelper(q, k, scale, attentionLogits);
if(vMask != nullptr && !vMask->isEmpty() && vMask->rankOf() < v->rankOf()) {
vmaskInternal = expandDims.evaluate({vMask},{},{-2}).at(0);
}
if(useCausalMask) {
std::vector<LongType> causalMaskShape2;
causalMaskShape2.push_back(attentionScores->sizeAt(0));
//4d
if(attentionScores->rankOf() > 3)
causalMaskShape2.push_back(attentionScores->sizeAt(1));
for(int i = attentionScores->rankOf() - 2; i < attentionScores->rankOf(); i++) {
causalMaskShape2.push_back(attentionScores->sizeAt(i));
}
casualPointer = lowerTriangularMask(&causalMaskShape2);
}
auto scoresMask = mergeMasks(vmaskInternal,casualPointer);
//compute actual softmax now
if(training) {
applyAttentionScores(attentionScores, v, scoresMask, dropout, dropoutSeed, applyScoresOut, attentionLogits,
dropoutMask);
} else {
applyAttentionScores(attentionScores, v, scoresMask, 0, dropoutSeed, applyScoresOut, attentionLogits, dropoutMask);
}
//inputs: scores: batch size tq tv value:batch size, tv,dim scoresmask: batch size 1 tv or batch size tq tv
if(qMask != nullptr && !qMask->isEmpty()) {
qMaskInternal = expandDims.evaluate({qMaskInternal},{},{-1}).at(0);
auto casted = qMaskInternal->cast(attentionScores->dataType());
*attentionScores *= *casted;
}
}
void AttentionHelper::multiHeadProjectBp(NDArray *input, NDArray *projectionMatrix,
NDArray *eps,
NDArray *dLdInput, NDArray *dLdProjectionMatrix, LaunchContext *context) {
auto miniBatchSize = input->sizeAt(0);
auto seqLength = input->sizeAt(2);
auto numHeads = projectionMatrix->sizeAt(0);
auto projectedSize = projectionMatrix->sizeAt(1);
std::vector<sd::LongType> epsPermVec = {1, 2, 0, 3};
auto epsPerm = eps->permute(epsPermVec, false, false);
std::vector<sd::LongType> epsReshapeVec = {numHeads * projectedSize, miniBatchSize * seqLength};
auto epsReshaped = epsPerm->reshape('c', epsReshapeVec);
std::vector<sd::LongType> inputPermVec = {1, 0, 2};
auto inputPerm = input->permute(inputPermVec, false, false);
std::vector<sd::LongType> inputPermShape = {input->sizeAt(1), miniBatchSize * seqLength};
auto inputPrep = inputPerm->reshape('c',inputPermShape,false);
std::vector<sd::LongType> projectionMatrixShape = {numHeads * projectionMatrix->sizeAt(1), projectionMatrix->sizeAt(2)};
auto projectionPrep =
projectionMatrix->reshape('c', projectionMatrixShape);
ops::matmul_bp mmulBp;
NDArray dLdProjectionPrep(projectionPrep->shapeInfo(), false, context);
NDArray dLdInputPrep(inputPrep->shapeInfo(), false, context);
mmulBp.execute({projectionPrep, inputPrep, epsReshaped}, std::vector<NDArray *>{&dLdProjectionPrep, &dLdInputPrep},
{}, {}, {});
dLdProjectionPrep.reshapei({numHeads, projectionMatrix->sizeAt(1), projectionMatrix->sizeAt(2)});
dLdProjectionMatrix->assign(&dLdProjectionPrep);
dLdInputPrep.reshapei({input->sizeAt(1), miniBatchSize, seqLength});
dLdInputPrep.permutei({1, 0, 2}, false, false);
dLdInput->assign(&dLdInputPrep);
delete epsReshaped;
delete projectionPrep;
}
} // namespace sd
#endif
#endif
@@ -0,0 +1,75 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by raver119 on 10.11.2017.
//
#include <helpers/BitwiseUtils.h>
#include <helpers/logger.h>
#include <types/float16.h>
namespace sd {
bool BitwiseUtils::isBE() {
short int word = 0x0001;
char *byte = (char *)&word;
return (byte[0] ? false : true);
}
int BitwiseUtils::valueBit(int holder) {
if (holder == 0) return -1;
#ifdef REVERSE_BITS
for (int e = 32; e >= 0; e--) {
#else
for (int e = 0; e < 32; e++) {
#endif
bool isOne = (holder & 1 << e) != 0;
if (isOne) return e;
}
return -1;
}
std::vector<LongType> BitwiseUtils::valueBits(int holder) {
std::vector<LongType> bits;
if (holder == 0) {
for (int e = 0; e < 32; e++) bits.emplace_back(0);
return bits;
}
#ifdef REVERSE_BITS
for (int e = 32; e >= 0; e--) {
#else
for (int e = 0; e < 32; e++) {
#endif
bool isOne = (holder & 1 << e) != 0;
if (isOne)
bits.emplace_back(1);
else
bits.emplace_back(0);
}
return bits;
}
ByteOrder BitwiseUtils::asByteOrder() { return isBE() ? BE : LE; }
} // namespace sd
+523
View File
@@ -0,0 +1,523 @@
/* ******************************************************************************
*
*
* 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
//
#include <helpers/BlasHelper.h>
#include <cstdlib>
#include <string>
// OpenBLAS thread control function declaration
#if HAVE_OPENBLAS
extern "C" void openblas_set_num_threads(int num_threads);
extern "C" int openblas_get_num_threads(void);
#endif
namespace sd {
BlasHelper::BlasHelper() {
// Initialize BLAS threading configuration from environment
initializeBlasThreading();
}
BlasHelper &BlasHelper::getInstance() {
static BlasHelper instance;
return instance;
}
void BlasHelper::initializeFunctions(Pointer *functions) {
sd_debug("Initializing BLAS\n", "");
_hasSgemv = functions[0] != nullptr;
_hasSgemm = functions[2] != nullptr;
_hasDgemv = functions[1] != nullptr;
_hasDgemm = functions[3] != nullptr;
_hasSgemmBatch = functions[4] != nullptr;
_hasDgemmBatch = functions[5] != nullptr;
#if !defined(SD_CUDA)
this->cblasSgemv = (CblasSgemv)functions[0];
this->cblasDgemv = (CblasDgemv)functions[1];
this->cblasSgemm = (CblasSgemm)functions[2];
this->cblasDgemm = (CblasDgemm)functions[3];
this->cblasSgemmBatch = (CblasSgemmBatch)functions[4];
this->cblasDgemmBatch = (CblasDgemmBatch)functions[5];
this->lapackeSgesvd = (LapackeSgesvd)functions[6];
this->lapackeDgesvd = (LapackeDgesvd)functions[7];
this->lapackeSgesdd = (LapackeSgesdd)functions[8];
this->lapackeDgesdd = (LapackeDgesdd)functions[9];
#endif
}
void BlasHelper::initializeDeviceFunctions(Pointer *functions) {
sd_debug("Initializing device BLAS\n", "");
}
#if defined(HAS_FLOAT32)
template <>
bool BlasHelper::hasGEMV<float>() {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasSgemv;
#endif
}
#endif
#if defined(HAS_DOUBLE)
template <>
bool BlasHelper::hasGEMV<double>() {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasDgemv;
#endif
}
#endif
#if defined(HAS_FLOAT16)
template <>
bool BlasHelper::hasGEMV<float16>() {
return false;
}
#endif
#if defined(HAS_BFLOAT16)
template <>
bool BlasHelper::hasGEMV<bfloat16>() {
return false;
}
#endif
#if defined(HAS_BOOL)
template <>
bool BlasHelper::hasGEMV<bool>() {
return false;
}
#endif
#if defined(HAS_INT32)
template <>
bool BlasHelper::hasGEMV<int>() {
return false;
}
#endif
#if defined(HAS_INT8)
template <>
bool BlasHelper::hasGEMV<int8_t>() {
return false;
}
#endif
#if defined(HAS_UINT8)
template <>
bool BlasHelper::hasGEMV<uint8_t>() {
return false;
}
#endif
#if defined(HAS_INT16)
template <>
bool BlasHelper::hasGEMV<int16_t>() {
return false;
}
#endif
#if defined(HAS_LONG)
template <>
bool BlasHelper::hasGEMV<LongType>() {
return false;
}
#endif
bool BlasHelper::hasGEMV(const DataType dtype) {
#if defined(HAS_FLOAT32)
if (dtype == FLOAT32) {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasSgemv;
#endif
}
#endif
#if defined(HAS_DOUBLE)
if (dtype == DOUBLE) {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasDgemv;
#endif
}
#endif
return false;
}
#if defined(HAS_FLOAT32)
template <>
bool BlasHelper::hasGEMM<float>() {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasSgemm;
#endif
}
#endif
#if defined(HAS_DOUBLE)
template <>
bool BlasHelper::hasGEMM<double>() {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasDgemm;
#endif
}
#endif
#if defined(HAS_FLOAT16)
template <>
bool BlasHelper::hasGEMM<float16>() {
return false;
}
#endif
#if defined(HAS_BFLOAT16)
template <>
bool BlasHelper::hasGEMM<bfloat16>() {
return false;
}
#endif
#if defined(HAS_INT32)
template <>
bool BlasHelper::hasGEMM<int>() {
return false;
}
#endif
#if defined(HAS_UINT8)
template <>
bool BlasHelper::hasGEMM<uint8_t>() {
return false;
}
#endif
#if defined(HAS_INT8)
template <>
bool BlasHelper::hasGEMM<int8_t>() {
return false;
}
#endif
#if defined(HAS_INT16)
template <>
bool BlasHelper::hasGEMM<int16_t>() {
return false;
}
#endif
#if defined(HAS_BOOL)
template <>
bool BlasHelper::hasGEMM<bool>() {
return false;
}
#endif
#if defined(HAS_LONG)
template <>
bool BlasHelper::hasGEMM<LongType>() {
return false;
}
#endif
bool BlasHelper::hasGEMM(const DataType dtype) {
#if defined(HAS_FLOAT32)
if (dtype == FLOAT32) {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasSgemm;
#endif
}
#endif
#if defined(HAS_DOUBLE)
if (dtype == DOUBLE) {
if (Environment::getInstance().blasFallback()) return false;
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return true;
#else
return _hasDgemm;
#endif
}
#endif
return false;
}
#if defined(HAS_FLOAT32)
template <>
bool BlasHelper::hasBatchedGEMM<float>() {
if (Environment::getInstance().blasFallback()) return false;
return _hasSgemmBatch;
}
#endif
#if defined(HAS_DOUBLE)
template <>
bool BlasHelper::hasBatchedGEMM<double>() {
if (Environment::getInstance().blasFallback()) return false;
return _hasDgemmBatch;
}
#endif
#if defined(HAS_FLOAT16)
template <>
bool BlasHelper::hasBatchedGEMM<float16>() {
return false;
}
#endif
#if defined(HAS_BFLOAT16)
template <>
bool BlasHelper::hasBatchedGEMM<bfloat16>() {
return false;
}
#endif
#if defined(HAS_LONG)
template <>
bool BlasHelper::hasBatchedGEMM<LongType>() {
return false;
}
#endif
#if defined(HAS_INT32)
template <>
bool BlasHelper::hasBatchedGEMM<int>() {
return false;
}
#endif
#if defined(HAS_INT8)
template <>
bool BlasHelper::hasBatchedGEMM<int8_t>() {
return false;
}
#endif
#if defined(HAS_UINT8)
template <>
bool BlasHelper::hasBatchedGEMM<uint8_t>() {
return false;
}
#endif
#if defined(HAS_INT16)
template <>
bool BlasHelper::hasBatchedGEMM<int16_t>() {
return false;
}
#endif
#if defined(HAS_BOOL)
template <>
bool BlasHelper::hasBatchedGEMM<bool>() {
return false;
}
#endif
#if !defined(SD_CUDA)
#if defined(HAS_FLOAT32)
CblasSgemv BlasHelper::sgemv() {
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return (CblasSgemv)&cblas_sgemv;
#else
return this->cblasSgemv;
#endif
}
CblasSgemm BlasHelper::sgemm() {
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return (CblasSgemm)&cblas_sgemm;
#else
return this->cblasSgemm;
#endif
}
CblasSgemmBatch BlasHelper::sgemmBatched() { return this->cblasSgemmBatch; }
LapackeSgesvd BlasHelper::sgesvd() { return this->lapackeSgesvd; }
LapackeSgesdd BlasHelper::sgesdd() { return this->lapackeSgesdd; }
#endif
#if defined(HAS_DOUBLE)
CblasDgemv BlasHelper::dgemv() {
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return (CblasDgemv)&cblas_dgemv;
#else
return this->cblasDgemv;
#endif
}
CblasDgemm BlasHelper::dgemm() {
#if __EXTERNAL_BLAS__ || HAVE_OPENBLAS
return (CblasDgemm)&cblas_dgemm;
#else
return this->cblasDgemm;
#endif
}
CblasDgemmBatch BlasHelper::dgemmBatched() { return this->cblasDgemmBatch; }
LapackeDgesvd BlasHelper::dgesvd() { return this->lapackeDgesvd; }
LapackeDgesdd BlasHelper::dgesdd() { return this->lapackeDgesdd; }
#endif
#endif
// BLAS call serialization implementation
std::unique_lock<std::mutex> BlasHelper::lockBlas() const {
if (_serializeBlasCalls.load()) {
return std::unique_lock<std::mutex>(_blasMutex);
}
// Return an unlocked lock if serialization is disabled
return std::unique_lock<std::mutex>(_blasMutex, std::defer_lock);
}
bool BlasHelper::isSerializeBlasCalls() const {
return _serializeBlasCalls.load();
}
void BlasHelper::setSerializeBlasCalls(bool serialize) {
_serializeBlasCalls.store(serialize);
}
int BlasHelper::getOpenblasThreads() const {
return _openblasThreads.load();
}
void BlasHelper::setOpenblasThreads(int threads) {
_openblasThreads.store(threads);
#if HAVE_OPENBLAS
if (threads > 0) {
openblas_set_num_threads(threads);
sd_debug("OpenBLAS threads set to %d\n", threads);
}
#endif
}
void BlasHelper::initializeBlasThreading() {
// Check SD_BLAS_SERIALIZE environment variable
// Default is true (serialization enabled) for OpenBLAS safety
const char* serializeEnv = std::getenv("SD_BLAS_SERIALIZE");
if (serializeEnv != nullptr) {
std::string val(serializeEnv);
if (val == "0" || val == "false" || val == "FALSE" || val == "no" || val == "NO") {
_serializeBlasCalls.store(false);
sd_debug("BLAS call serialization DISABLED via SD_BLAS_SERIALIZE=%s\n", serializeEnv);
} else {
_serializeBlasCalls.store(true);
sd_debug("BLAS call serialization ENABLED via SD_BLAS_SERIALIZE=%s\n", serializeEnv);
}
} else {
// Default: enable serialization for OpenBLAS safety
_serializeBlasCalls.store(true);
sd_debug("BLAS call serialization ENABLED by default (set SD_BLAS_SERIALIZE=0 to disable)\n", "");
}
// Check SD_OPENBLAS_THREADS environment variable for OpenBLAS thread count
// This is separate from the serialization - you can have both:
// - Serialization ON + multi-threaded OpenBLAS = safe concurrent BLAS with internal parallelism
// - Serialization OFF + single-threaded OpenBLAS = original behavior
const char* threadsEnv = std::getenv("SD_OPENBLAS_THREADS");
if (threadsEnv != nullptr) {
#ifdef __cpp_exceptions
try {
int threads = std::stoi(std::string(threadsEnv));
if (threads > 0) {
_openblasThreads.store(threads);
#if HAVE_OPENBLAS
openblas_set_num_threads(threads);
sd_debug("OpenBLAS threads set to %d via SD_OPENBLAS_THREADS\n", threads);
#endif
}
} catch (...) {
// Invalid value, ignore
}
#else
int threads = std::atoi(threadsEnv);
if (threads > 0) {
_openblasThreads.store(threads);
#if HAVE_OPENBLAS
openblas_set_num_threads(threads);
sd_debug("OpenBLAS threads set to %d via SD_OPENBLAS_THREADS\n", threads);
#endif
}
#endif
}
// Also check OPENBLAS_NUM_THREADS (standard OpenBLAS env var) if SD_OPENBLAS_THREADS not set
if (_openblasThreads.load() == 0) {
const char* openblasEnv = std::getenv("OPENBLAS_NUM_THREADS");
if (openblasEnv != nullptr) {
#ifdef __cpp_exceptions
try {
int threads = std::stoi(std::string(openblasEnv));
if (threads > 0) {
_openblasThreads.store(threads);
sd_debug("OpenBLAS threads detected from OPENBLAS_NUM_THREADS=%d\n", threads);
}
} catch (...) {
// Invalid value, ignore
}
#else
int threads = std::atoi(openblasEnv);
if (threads > 0) {
_openblasThreads.store(threads);
sd_debug("OpenBLAS threads detected from OPENBLAS_NUM_THREADS=%d\n", threads);
}
#endif
}
}
}
// destructor
BlasHelper::~BlasHelper() noexcept {}
} // namespace sd
@@ -0,0 +1,447 @@
/* ******************************************************************************
*
* Copyright (c) 2024 Konduit K.K.
* 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.
*
* 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
******************************************************************************/
#include "helpers/ConstantShapeHelper.h"
#include "array/ConstantShapeBuffer.h"
#include "system/common.h"
#include <array/PrimaryPointerDeallocator.h>
#include <helpers/ConstantShapeHelper.h>
#include <helpers/ShapeBuilders.h>
#include <helpers/ShapeUtils.h>
#include <helpers/shape.h>
#include <system/Environment.h>
namespace sd {
ConstantShapeHelper::~ConstantShapeHelper() {
}
ConstantShapeHelper::ConstantShapeHelper() {
}
ConstantShapeHelper& ConstantShapeHelper::getInstance() {
static ConstantShapeHelper instance;
return instance;
}
void ConstantShapeHelper::initializeEarly() {
ConstantShapeHelper& instance = getInstance();
instance._shapeTrie.waitForInitialization();
}
ConstantShapeBuffer* ConstantShapeHelper::createConstBuffFromExisting(sd::LongType* shapeInfo) {
auto result = bufferForShapeInfo(shapeInfo);
return result;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfo(LongType* shapeInfo) {
if(shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
if(shape::rank(shapeInfo) < 0 || shape::rank(shapeInfo) > SD_MAX_RANK) {
THROW_EXCEPTION("shapeInfo is not a valid rank.");
}
auto buffer = _shapeTrie.getOrCreate(shapeInfo);
if (buffer == nullptr || buffer->primary() == nullptr) {
THROW_EXCEPTION("Failed to get/create shape buffer");
}
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::createSubArrShapeInfo( sd::LongType* inShapeInfo, LongType* dims,
sd::LongType dimsSize) {
sd::LongType* newShapeInfo = ShapeBuilders::createSubArrShapeInfo(inShapeInfo, dims, dimsSize, nullptr);
auto ret = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return ret;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfo(DataType dataType, char order,
const std::vector<LongType>& shape) {
auto descriptor = ShapeBuilders::createShapeInfo(dataType, order, shape);
auto result = bufferForShapeInfo(descriptor);
delete[] descriptor;
return result;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfo(DataType dataType, char order,
int rank, LongType* shape) {
auto descriptor = ShapeBuilders::createShapeInfo(dataType, order, rank, shape, nullptr, false);
auto result = bufferForShapeInfo(descriptor);
delete[] descriptor;
return result;
}
LongType* ConstantShapeHelper::emptyShapeInfoWithShape(DataType dataType, std::vector<LongType>& shape) {
auto descriptor = ShapeBuilders::createShapeInfo(dataType, 'c', shape, nullptr);
ArrayOptions::setPropertyBit(descriptor, ARRAY_EMPTY);
auto existing = createFromExisting(descriptor);
delete[] descriptor;
return existing;
}
LongType* ConstantShapeHelper::createShapeInfo(DataType dataType, char order,
const std::vector<LongType>& shape) {
auto descriptor = ShapeBuilders::createShapeInfo(dataType, order, shape);
auto result = bufferForShapeInfo(descriptor)->primary();
delete[] descriptor;
return result;
}
LongType* ConstantShapeHelper::createShapeInfo(DataType dataType, char order, int rank,
LongType* shape, LongType extraProperties) {
if (extraProperties < 0) {
extraProperties = ArrayOptions::flagForDataType(dataType);
}
std::unique_ptr<LongType[]> strides(order == 'c' ? shape::calcStrides(shape, rank)
: shape::calcStridesFortran(shape, rank));
auto descriptor = ShapeBuilders::createShapeInfo(dataType, order, rank, shape, strides.get(),
nullptr, extraProperties);
auto ret = bufferForShapeInfo(descriptor)->primary();
ArrayOptions::validateSingleDataType(ArrayOptions::dataType(ret));
delete[] descriptor;
return ret;
}
LongType* ConstantShapeHelper::createShapeInfo(DataType dataType, LongType* shapeInfo) {
auto result = createShapeInfo(dataType, shape::order(shapeInfo), shape::rank(shapeInfo),
shape::shapeOf(const_cast<LongType*>(shapeInfo)), -1);
return result;
}
LongType* ConstantShapeHelper::emptyShapeInfo(DataType dataType) {
auto descriptor = ShapeBuilders::emptyShapeInfo(dataType);
auto result = bufferForShapeInfo(descriptor)->primary();
delete[] descriptor;
return result;
}
LongType* ConstantShapeHelper::scalarShapeInfo(DataType dataType) {
auto descriptor = ShapeBuilders::createScalarShapeInfo(dataType);
return bufferForShapeInfo(descriptor)->primary();
}
LongType* ConstantShapeHelper::vectorShapeInfo(LongType length, DataType dataType) {
auto descriptor = ShapeBuilders::createVectorShapeInfo(dataType, length);
auto result = bufferForShapeInfo(descriptor)->primary();
delete[] descriptor;
return result;
}
LongType* ConstantShapeHelper::createShapeInfo(ShapeDescriptor* descriptor) {
auto shapeInfo = descriptor->toShapeInfo();
auto result = bufferForShapeInfo(shapeInfo)->primary();
delete[] shapeInfo;
return result;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithView(LongType* shapeInfo) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
ArrayOptions::setPropertyBit(newShapeInfo, ARRAY_IS_VIEW);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithoutView(LongType* shapeInfo) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
ArrayOptions::unsetPropertyBit(newShapeInfo, ARRAY_IS_VIEW);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithNeedsCopy(LongType* shapeInfo) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
ArrayOptions::setPropertyBit(newShapeInfo, ARRAY_NEEDS_COPY);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithoutNeedsCopy(LongType* shapeInfo) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
ArrayOptions::unsetPropertyBit(newShapeInfo, ARRAY_NEEDS_COPY);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithCopyOffset(LongType* shapeInfo, int inputIndex) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
if (inputIndex < 0 || inputIndex > 10) {
THROW_EXCEPTION("Input index out of range [0-10]");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
LongType flag = ArrayOptions::copyOffsetFlagForInput(inputIndex);
ArrayOptions::setPropertyBit(newShapeInfo, flag);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithoutCopyOffset(LongType* shapeInfo, int inputIndex) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
if (inputIndex < 0 || inputIndex > 10) {
THROW_EXCEPTION("Input index out of range [0-10]");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
LongType flag = ArrayOptions::copyOffsetFlagForInput(inputIndex);
ArrayOptions::unsetPropertyBit(newShapeInfo, flag);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithoutAllCopyOffsets(LongType* shapeInfo) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
ArrayOptions::clearAllCopyOffsets(newShapeInfo);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoWithFlags(LongType* shapeInfo,
LongType flagsToSet,
LongType flagsToUnset) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
// Unset flags first
if (flagsToUnset != 0) {
LongType extraIdx = ArrayOptions::extraIndex(newShapeInfo);
newShapeInfo[extraIdx] = newShapeInfo[extraIdx] & ~flagsToUnset;
}
// Then set flags
if (flagsToSet != 0) {
LongType extraIdx = ArrayOptions::extraIndex(newShapeInfo);
newShapeInfo[extraIdx] = newShapeInfo[extraIdx] | flagsToSet;
}
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
ConstantShapeBuffer* ConstantShapeHelper::bufferForShapeInfoAsViewWithOffset(LongType* shapeInfo,
int inputIndex) {
if (shapeInfo == nullptr) {
THROW_EXCEPTION("shapeInfo is nullptr");
}
if (inputIndex < 0 || inputIndex > 10) {
THROW_EXCEPTION("Input index out of range [0-10]");
}
LongType* newShapeInfo = ShapeBuilders::copyShapeInfo(shapeInfo, false, nullptr);
// Set view flag
ArrayOptions::setPropertyBit(newShapeInfo, ARRAY_IS_VIEW);
// Set copy offset flag for specified input
LongType flag = ArrayOptions::copyOffsetFlagForInput(inputIndex);
ArrayOptions::setPropertyBit(newShapeInfo, flag);
auto buffer = bufferForShapeInfo(newShapeInfo);
delete[] newShapeInfo;
return buffer;
}
LongType* ConstantShapeHelper::createFromExisting(LongType* shapeInfo) {
if (!shapeInfo) {
THROW_EXCEPTION("Null shape info");
}
auto buffer = bufferForShapeInfo(shapeInfo);
return buffer->primary();
}
LongType* ConstantShapeHelper::castToDataType(LongType* shapeInfo, DataType newType) {
if (!shapeInfo) {
THROW_EXCEPTION("Null shape info");
}
if (ArrayOptions::dataType(shapeInfo) == newType) {
return shapeInfo;
}
auto tempShapeInfo = ShapeBuilders::copyShapeInfoWithNewType(shapeInfo, newType);
if (!tempShapeInfo) {
THROW_EXCEPTION("Failed to create temp shape info");
}
auto buffer = bufferForShapeInfo(tempShapeInfo);
auto result = buffer->primary();
delete[] tempShapeInfo;
if(ArrayOptions::dataType(result) != newType) {
std::string errorMessage;
errorMessage += "castToDataType: new data type is ";
errorMessage += DataTypeUtils::asString(newType);
errorMessage += " data type from new constant created data type ";
errorMessage += DataTypeUtils::asString(ArrayOptions::dataType(result));
errorMessage += "\n";
THROW_EXCEPTION(errorMessage.c_str());
}
return result;
}
ConstantShapeBuffer* ConstantShapeHelper::createShapeInfoWithUnitiesForBroadcast(sd::LongType* maxShapeInfo,
sd::LongType* minShapeInfo,
sd::memory::Workspace* workspace,
const std::vector<LongType>& dimensions) {
sd::LongType* newShapeInfo = nullptr;
ALLOCATE(newShapeInfo, workspace, shape::shapeInfoLength(shape::rank(maxShapeInfo)), sd::LongType);
newShapeInfo[0] = shape::rank(maxShapeInfo);
newShapeInfo[2 * shape::rank(maxShapeInfo) + 1] = 0;
sd::ArrayOptions::copyDataType(newShapeInfo, minShapeInfo); // type
newShapeInfo[2 * newShapeInfo[0] + 2] = shape::elementWiseStride(minShapeInfo); // ews
newShapeInfo[2 * newShapeInfo[0] + 3] = shape::order(minShapeInfo); // order
if (!dimensions.empty()) {
for (sd::LongType k = 0, j = 0, i = 0; i < shape::rank(maxShapeInfo); ++i) {
if (j < static_cast<sd::LongType>(dimensions.size()) && dimensions[j] == i) {
shape::shapeOf(newShapeInfo)[i] = shape::shapeOf(minShapeInfo)[k];
shape::stride(newShapeInfo)[i] = shape::stride(minShapeInfo)[k++];
++j;
} else {
shape::shapeOf(newShapeInfo)[i] = 1;
shape::stride(newShapeInfo)[i] = 0;
if (shape::sizeAt(minShapeInfo, k) == 1 && static_cast<sd::LongType>(dimensions.size()) != shape::rank(minShapeInfo)) ++k;
}
}
} else {
for (int j = shape::rank(minShapeInfo) - 1, i = shape::rank(maxShapeInfo) - 1; i >= 0; --i) {
if (j >= 0) {
shape::shapeOf(newShapeInfo)[i] = shape::shapeOf(minShapeInfo)[j];
shape::stride(newShapeInfo)[i] = shape::shapeOf(minShapeInfo)[j] == 1 ? 0 : shape::stride(minShapeInfo)[j];
--j;
} else {
shape::shapeOf(newShapeInfo)[i] = 1;
shape::stride(newShapeInfo)[i] = 0;
}
}
}
auto ret = bufferForShapeInfo(newShapeInfo);
RELEASE(newShapeInfo, workspace);
return ret;
}
ConstantShapeBuffer* ConstantShapeHelper::createShapeInfoWithNoUnitiesForReduce(const sd::LongType* maxShapeInfo,
const std::vector<LongType>* dimsWithUnities,
sd::memory::Workspace* workspace) {
sd::LongType* newShapeInfo = nullptr;
ALLOCATE(newShapeInfo, workspace, shape::shapeInfoLength(shape::rank(maxShapeInfo) - dimsWithUnities->size()),
sd::LongType);
sd::LongType temp;
if (dimsWithUnities->size() == 1 && shape::isCommonVector(maxShapeInfo, temp) && temp == dimsWithUnities->at(0)) {
auto dims = ShapeUtils::evalDimsToExclude(shape::rank(maxShapeInfo), 1,&temp);
shape::excludeUnitiesFromShapeInfo(maxShapeInfo, dims->data(), dims->size(), newShapeInfo);
delete dims;
} else {
shape::excludeUnitiesFromShapeInfo(maxShapeInfo, dimsWithUnities->data(), dimsWithUnities->size(), newShapeInfo);
}
auto ret = bufferForShapeInfo(newShapeInfo);
RELEASE(newShapeInfo, workspace);
return ret;
}
void ConstantShapeHelper::clearCache() {
std::lock_guard<std::mutex> lock(_mutex);
_shapeTrie.clearCache();
}
LongType ConstantShapeHelper::getCachedEntries() const {
return _shapeTrie.getCachedEntries();
}
LongType ConstantShapeHelper::getCachedBytes() const {
return _shapeTrie.getCachedBytes();
}
LongType ConstantShapeHelper::getPeakCachedEntries() const {
return _shapeTrie.getPeakCachedEntries();
}
LongType ConstantShapeHelper::getPeakCachedBytes() const {
return _shapeTrie.getPeakCachedBytes();
}
std::string ConstantShapeHelper::toString(int maxDepth, int maxEntries) const {
return _shapeTrie.toString(maxDepth, maxEntries);
}
void ConstantShapeHelper::getCachedPointers(std::unordered_set<void*>& out_pointers) const {
_shapeTrie.getCachedPointers(out_pointers);
}
} // namespace sd
@@ -0,0 +1,140 @@
/* ******************************************************************************
*
* Copyright (c) 2024 Konduit K.K.
* 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.
*
* 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
******************************************************************************/
#include <array/TadDescriptor.h>
#include <array/TadPack.h>
#include <helpers/ConstantTadHelper.h>
#include <helpers/ShapeUtils.h>
namespace sd {
ConstantTadHelper& ConstantTadHelper::getInstance() {
static ConstantTadHelper instance;
return instance;
}
std::shared_ptr<TadPack> ConstantTadHelper::tadForDimensions(LongType* originalShape, LongType dimension) {
return tadForDimensions(originalShape, &dimension, 1);
}
std::shared_ptr<TadPack> ConstantTadHelper::tadForDimensions(LongType* originalShape, std::vector<LongType>* dimensions) {
if (dimensions == nullptr) {
THROW_EXCEPTION("Dimensions vector is null");
}
return tadForDimensions(originalShape, const_cast<LongType*>(dimensions->data()), dimensions->size());
}
std::shared_ptr<TadPack> ConstantTadHelper::tadForDimensions(TadDescriptor* descriptor) {
if (descriptor == nullptr) {
THROW_EXCEPTION("TadDescriptor is null");
}
return tadForDimensions(descriptor->originalShape(), descriptor->axis().data(),
descriptor->axis().size());
}
std::shared_ptr<TadPack> ConstantTadHelper::tadForDimensions(LongType* originalShape, LongType* dimensions, LongType dimLength) {
if (originalShape == nullptr) {
THROW_EXCEPTION("Original shape is null");
}
if (dimensions == nullptr && dimLength > 0) {
THROW_EXCEPTION("Dimensions array is null but dimLength > 0");
}
// Check for empty array
if (shape::isEmptyConst(originalShape)) {
THROW_EXCEPTION("Cannot create TADs for empty array");
}
sd::LongType rank = shape::rank(originalShape);
if (rank < 0) {
THROW_EXCEPTION("Invalid shape rank");
}
// Check for zero-sized dimensions
for (LongType i = 0; i < rank; i++) {
if (shape::sizeAt(originalShape, i) == 0) {
THROW_EXCEPTION("Cannot create TADs for array with zero-sized dimensions");
}
}
// Handle zero dimension length case - treat entire array as single TAD
if (dimLength <= 0) {
// When no dimensions specified, create TAD along all dimensions
// This means the entire array is treated as a single TAD
std::vector<LongType> allDims;
for (LongType i = 0; i < rank; i++) {
allDims.push_back(i);
}
// Recursively call with all dimensions
return tadForDimensions(originalShape, allDims.data(), rank);
}
// Additional validation: check if dimensions are within valid range
for (LongType i = 0; i < dimLength; i++) {
LongType dim = dimensions[i];
if (dim < 0) dim += rank; // Handle negative dimensions
if (dim < 0 || dim >= rank) {
THROW_EXCEPTION("Dimension index is out of bounds");
}
}
// Create non-temporary vector to satisfy the reference requirement
std::vector<LongType> dims(dimensions, dimensions + dimLength);
// The shared_ptr keeps the TadPack alive even if the cache tries to clear it
std::shared_ptr<TadPack> result = nullptr;
try {
result = _trie.getOrCreate(dims, originalShape);
} catch (const std::exception& e) {
THROW_EXCEPTION("Failed to create or retrieve TAD pack");
}
// DO NOT call checkAndCleanupCaches() here - would delete the pack we just created!
// Cleanup happens at NativeOps layer AFTER operations complete.
return result;
}
void ConstantTadHelper::clearCache() {
_trie.clear();
}
LongType ConstantTadHelper::getCachedEntries() const {
return _trie.getCachedEntries();
}
LongType ConstantTadHelper::getCachedBytes() const {
return _trie.getCachedBytes();
}
LongType ConstantTadHelper::getPeakCachedEntries() const {
return _trie.getPeakCachedEntries();
}
LongType ConstantTadHelper::getPeakCachedBytes() const {
return _trie.getPeakCachedBytes();
}
std::string ConstantTadHelper::toString(int maxDepth, int maxEntries) const {
return _trie.toString(maxDepth, maxEntries);
}
void ConstantTadHelper::getCachedPointers(std::unordered_set<void*>& out_pointers) const {
_trie.getCachedPointers(out_pointers);
}
} // namespace sd
@@ -0,0 +1,35 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by raver on 4/5/2018.
//
#include <helpers/CudaLaunchHelper.h>
#include <math/templatemath.h>
namespace sd {
int CudaLaunchHelper::getReductionBlocks(LongType xLength, int blockSize) {
int div = xLength / blockSize;
int can = sd::math::sd_max<int>(div, 1);
if (xLength % blockSize != 0 && xLength > blockSize) can++;
// not more then 512 blocks
return sd::math::sd_min<int>(can, 512);
}
} // namespace sd
@@ -0,0 +1,109 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by raver119 on 20/04/18.
//
#include <array/NDArray.h>
#include <array/NDArrayFactory.h>
#include <execution/Threads.h>
#include <helpers/DebugHelper.h>
#include <helpers/DebugInfo.h>
#include <ops/declarable/headers/parity_ops.h>
namespace sd {
DebugInfo DebugHelper::debugStatistics(NDArray * input) {
DebugInfo info;
retrieveDebugStatistics(&info, const_cast<NDArray*>(input));
return info;
}
void DebugHelper::retrieveDebugStatistics(DebugInfo* info, NDArray* input) {
if (nullptr == info) return;
info->_minValue = 0.;
info->_maxValue = -1;
info->_meanValue = 0.;
info->_stdDevValue = 1.;
info->_zeroCount = 0;
info->_positiveCount = 0;
info->_negativeCount = 0;
info->_infCount = 0;
info->_nanCount = 0;
if (input->lengthOf() == 1) { // scalar case
info->_minValue = input->e<double>(0);
info->_maxValue = info->_minValue;
info->_meanValue = info->_minValue;
info->_stdDevValue = info->_minValue;
info->_zeroCount = math::sd_abs<double,double>(input->e<double>(0)) > 0.00001 ? 0 : 1;
info->_positiveCount = input->e<double>(0) > 0 ? 1 : 0;
info->_negativeCount = input->e<double>(0) < 0 ? 1 : 0;
info->_infCount = math::sd_isinf(input->e<double>(0));
info->_nanCount = math::sd_isnan(input->e<double>(0));
} else if (input->lengthOf() > 0) {
// TO DO: here processing for all elements with array
auto _minValue = input->e<double>(0);
auto _maxValue = input->e<double>(0);
auto _meanValue = input->e<double>(0);
auto _stdDevValue = 0.; // info->_minValue;
auto _zeroCount = math::sd_abs<double,double>(input->e<double>(0)) > 0.00001 ? 0L : 1L;
auto _positiveCount = input->e<double>(0) > 0 ? 1L : 0L;
auto _negativeCount = input->e<double>(0) < 0 ? 1L : 0L;
auto _infCount = math::sd_isinf(input->e<double>(0)) ? 1L : 0L;
auto _nanCount = math::sd_isnan(input->e<double>(0)) ? 1L : 0L;
PRAGMA_OMP_PARALLEL_FOR_ARGS(schedule(guided) reduction(+:_nanCount,_infCount,_meanValue,_zeroCount,_positiveCount,_negativeCount) reduction(min:_minValue) reduction(max:_maxValue))
for (LongType e = 1; e < input->lengthOf(); e++) {
auto current = input->e<double>(e);
auto n = e + 1.;
// auto delta = current - _meanValue;
// auto delta2 = delta * delta;
_minValue = math::sd_min(current, _minValue);
_maxValue = math::sd_max(current, _maxValue);
_meanValue += current;
//_meanValue += delta / n; // this is a perfect formula but not working with omp in this notation
//_stdDevValue += delta2 * e / n;
_zeroCount += math::sd_abs<double,double>(current) > 0.00001 ? 0 : 1;
_positiveCount += current > 0 ? 1 : 0;
_negativeCount += current < 0 ? 1 : 0;
_infCount += math::sd_isinf(current);
_nanCount += math::sd_isnan(current);
}
*info = {_minValue, _maxValue, _meanValue / input->lengthOf(),
_stdDevValue, _zeroCount, _positiveCount,
_negativeCount, _infCount, _nanCount};
_stdDevValue = 0; // math::sd_sqrt<double, double>(info->_stdDevValue / (input->lengthOf() - 1));
auto func = PRAGMA_REDUCE_DOUBLE {
auto _stdDevValue = 0.0;
for (auto e = start; e < stop; e++) {
double current = input->e<double>(e);
_stdDevValue += (info->_meanValue - current) * (info->_meanValue - current); // info->_minValue;
}
return _stdDevValue;
};
_stdDevValue = samediff::Threads::parallel_double(
func, LAMBDA_AD { return _old + _new; }, 0, input->lengthOf());
info->_stdDevValue = math::sd_sqrt<double, double>(_stdDevValue / input->lengthOf());
}
// else - no statistics for empty
}
} // namespace sd
@@ -0,0 +1,867 @@
/* ******************************************************************************
*
* 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
******************************************************************************/
#include <array/ArrayOptions.hXX>
#include <array/ConstantShapeBuffer.h>
#include <array/DataType.h>
#include <array/PrimaryPointerDeallocator.h>
#include <helpers/DirectShapeTrie.h>
#include <helpers/shape.h>
#include <system/common.h>
#include <atomic>
#include <chrono>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include "helpers/ShapeBufferCreatorHelper.h"
#if defined(SD_GCC_FUNCTRACE)
#include <array/ShapeCacheLifecycleTracker.h>
#endif
namespace sd {
void DirectShapeTrie::waitForInitialization() const {
if (_initialization_complete.load(std::memory_order_acquire)) {
return;
}
int attempts = 0;
while (_initialization_in_progress.load(std::memory_order_acquire)) {
if (attempts < 10) {
std::this_thread::yield();
} else {
auto delay = std::chrono::microseconds(std::min<int>(500, attempts * 10));
std::this_thread::sleep_for(delay);
}
attempts++;
}
if (!_initialization_complete.load(std::memory_order_acquire)) {
THROW_EXCEPTION("DirectShapeTrie initialization did not complete before use");
}
}
void ShapeTrieNode::setBuffer(ConstantShapeBuffer* buf) {
if (!buf) return; // Nothing to do if buffer is null
// If we already have a buffer, don't replace it
if (_buffer != nullptr) {
// The existing buffer takes precedence
// Don't delete the new buffer - let the caller handle it
return;
}
// At this point, we know _buffer is null and buf is valid
// Set the buffer atomically
_buffer = buf;
}
#if defined(SD_GCC_FUNCTRACE)
void ShapeTrieNode::collectStoreStackTrace() {
this->storeStackTrace = backward::StackTrace();
this->storeStackTrace.load_here(32);
}
#endif
size_t DirectShapeTrie::computeHash(const LongType* shapeInfo) const {
size_t hash = 17; // Prime number starting point
const int rank = shape::rank(shapeInfo);
// Add rank first with high weight
hash = hash * 31 + rank * 19;
// Add shape elements to hash with position-dependent multipliers
const LongType* shape = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
hash = hash * 13 + static_cast<size_t>(shape[i]) * (7 + i);
}
// Add stride elements to hash with position-dependent multipliers
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
hash = hash * 19 + static_cast<size_t>(strides[i]) * (11 + i);
}
// Add data type and order with higher weights
hash = hash * 23 + static_cast<size_t>(ArrayOptions::dataType(shapeInfo)) * 29;
hash = hash * 37 + static_cast<size_t>(shape::order(shapeInfo)) * 41;
// Add total element count
hash = hash * 43 + shape::length(shapeInfo);
// **NEW: Add property flags to distinguish views from non-views**
hash = hash * 47 + static_cast<size_t>(shapeInfo[ArrayOptions::extraIndex(shapeInfo)]);
return hash;
}
int DirectShapeTrie::calculateShapeSignature(const LongType* shapeInfo) const {
int signature = 17;
const int rank = shape::rank(shapeInfo);
// Incorporate rank with weight
signature = signature * 31 + rank * 13;
// Incorporate shape dimensions with position weights
const LongType* shapeValues = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
signature = signature * 13 + static_cast<int>(shapeValues[i]) * (7 + i);
}
// Incorporate data type and order
signature = signature * 7 + static_cast<int>(ArrayOptions::dataType(shapeInfo)) * 11;
signature = signature * 17 + static_cast<int>(shape::order(shapeInfo)) * 19;
// Include element count
signature = signature * 23 + static_cast<int>(shape::length(shapeInfo) % 10000);
// **NEW: Include property flags**
signature = signature * 29 + static_cast<int>(shapeInfo[ArrayOptions::extraIndex(shapeInfo)] % 10000);
return signature;
}
size_t DirectShapeTrie::getStripeIndex(const LongType* shapeInfo) const {
return computeHash(shapeInfo) % NUM_STRIPES;
}
bool DirectShapeTrie::shapeInfoEqual(const LongType* a, const LongType* b) const {
if (a == b) return true;
if (a == nullptr || b == nullptr) return false;
const int rankA = shape::rank(a);
if (rankA != shape::rank(b)) return false;
const int len = shape::shapeInfoLength(rankA);
return std::memcmp(a, b, len * sizeof(LongType)) == 0;
}
void DirectShapeTrie::validateShapeInfo(const LongType* shapeInfo) const {
if (shapeInfo == nullptr) {
std::string msg = "Shape info cannot be null";
THROW_EXCEPTION(msg.c_str());
}
const int rank = shape::rank(shapeInfo);
if (rank < 0 || rank > SD_MAX_RANK) {
std::string errorMessage = "Invalid rank: " + std::to_string(rank) +
". Valid range is 0 to " + std::to_string(SD_MAX_RANK);
THROW_EXCEPTION(errorMessage.c_str());
}
if (rank == 0) {
const int len = shape::shapeInfoLength(rank);
bool allZero = true;
for (int i = 0; i < len; i++) {
if (shapeInfo[i] != 0) {
allZero = false;
break;
}
}
if (allZero) {
std::string msg = "Found shape buffer with all zero values. Values likely unset.";
THROW_EXCEPTION(msg.c_str());
}
}
if (ArrayOptions::dataType(shapeInfo) == UNKNOWN) {
std::string msg = "Shape info created with invalid data type";
THROW_EXCEPTION(msg.c_str());
}
char order = shape::order(shapeInfo);
if (order != 'c' && order != 'f') {
std::string errorMessage = "Invalid ordering in shape buffer: ";
errorMessage += order;
THROW_EXCEPTION(errorMessage.c_str());
}
}
const ShapeTrieNode* DirectShapeTrie::findChild(const ShapeTrieNode* node, LongType value,
int level, bool isShape, int shapeHash) const {
if (!node) return nullptr;
for (const auto& child : node->children()) {
if (child->value() == value &&
child->level() == level &&
child->isShape() == isShape &&
(shapeHash == 0 || child->shapeHash() == shapeHash)) {
return child;
}
}
return nullptr;
}
// Modified search method - still returns null when shape not found but with improved debugging
ConstantShapeBuffer* DirectShapeTrie::search(const LongType* shapeInfo, size_t stripeIdx) const {
// Validate input
if (shapeInfo == nullptr) {
std::string msg = "Null shapeInfo passed to search method";
THROW_EXCEPTION(msg.c_str());
}
if (stripeIdx >= NUM_STRIPES) {
std::string msg = "Invalid stripe index: " + std::to_string(stripeIdx) +
" (max: " + std::to_string(NUM_STRIPES - 1) + ")";
THROW_EXCEPTION(msg.c_str());
}
if (_roots == nullptr) {
std::string msg = "Root nodes array is null";
THROW_EXCEPTION(msg.c_str());
}
auto rootsRef = *_roots;
// No locks here - caller handles locking
const ShapeTrieNode* current = rootsRef[stripeIdx];
if (current == nullptr) {
// Cannot use createFallbackBuffer here as it's const method
// Caller should handle this case
return nullptr;
}
const int rank = shape::rank(shapeInfo);
const int shapeSignature = calculateShapeSignature(shapeInfo);
// Check rank
current = findChild(current, rank, 0, true, shapeSignature);
if (!current) {
return nullptr; // Not found, but this is expected behavior
}
// Check datatype
current = findChild(current, ArrayOptions::dataType(shapeInfo), 1, true, shapeSignature);
if (!current) {
return nullptr; // Not found, but this is expected behavior
}
// Check order
current = findChild(current, shape::order(shapeInfo), 2, true, shapeSignature);
if (!current) {
return nullptr; // Not found, but this is expected behavior
}
// Check shape values
const LongType* shapeValues = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
current = findChild(current, shapeValues[i], 3 + i, true, shapeSignature);
if (!current) {
return nullptr; // Not found, but this is expected behavior
}
}
// Check stride values
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
current = findChild(current, strides[i], 3 + rank + i, false, shapeSignature);
if (!current) {
return nullptr; // Not found, but this is expected behavior
}
}
return current ? current->buffer() : nullptr;
}
// Helper method to create a fallback buffer when the trie insertion fails
ConstantShapeBuffer* DirectShapeTrie::createFallbackBuffer(const LongType* shapeInfo, int rank) {
if (shapeInfo == nullptr) {
std::string msg = "Null shapeInfo passed to createFallbackBuffer";
THROW_EXCEPTION(msg.c_str());
}
if (rank < 0 || rank > SD_MAX_RANK) {
std::string msg = "Invalid rank in createFallbackBuffer: " + std::to_string(rank);
THROW_EXCEPTION(msg.c_str());
}
// Create a direct copy of the shape info
const int shapeInfoLength = shape::shapeInfoLength(rank);
LongType* shapeCopy = new LongType[shapeInfoLength];
if (shapeCopy == nullptr) {
std::string msg = "Failed to allocate memory for shape copy";
THROW_EXCEPTION(msg.c_str());
}
std::memcpy(shapeCopy, shapeInfo, shapeInfoLength * sizeof(LongType));
// Create a deallocator for memory management
auto deallocator = std::shared_ptr<PrimaryPointerDeallocator>(
new PrimaryPointerDeallocator(),
[] (PrimaryPointerDeallocator* ptr) { delete ptr; });
// Create a pointer wrapper and buffer
auto hPtr = new PointerWrapper(shapeCopy, deallocator);
if (hPtr == nullptr) {
delete[] shapeCopy;
std::string msg = "Failed to create PointerWrapper";
THROW_EXCEPTION(msg.c_str());
}
auto buffer = new ConstantShapeBuffer(hPtr);
if (buffer == nullptr) {
delete hPtr;
std::string msg = "Failed to create ConstantShapeBuffer";
THROW_EXCEPTION(msg.c_str());
}
#if defined(SD_GCC_FUNCTRACE)
// Track shape cache allocation
sd::array::ShapeCacheLifecycleTracker::getInstance().recordAllocation(shapeCopy);
#endif
// Fallback buffer is NOT cached, so refCount stays at 1 (caller owns it)
// Caller will call deleteConstantShapeBuffer() which calls release()
return buffer;
}
// Updated getOrCreate method to ensure it always creates a shape buffer
ConstantShapeBuffer* DirectShapeTrie::getOrCreate(const LongType* shapeInfo) {
waitForInitialization();
if (!shapeInfo) {
std::string msg = "Null shapeInfo passed to getOrCreate";
THROW_EXCEPTION(msg.c_str());
}
validateShapeInfo(shapeInfo);
size_t stripeIdx = getStripeIndex(shapeInfo);
int rank = shape::rank(shapeInfo);
// Validate stripe index
if (stripeIdx >= NUM_STRIPES) {
stripeIdx = NUM_STRIPES - 1;
}
int shapeSignature = calculateShapeSignature(shapeInfo);
// Check if mutex pointer is valid
if (_mutexes == nullptr || (*_mutexes)[stripeIdx] == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
// First try a read-only lookup without obtaining a write lock
{
SHARED_LOCK_TYPE<MUTEX_TYPE> readLock(*(*_mutexes)[stripeIdx]);
ConstantShapeBuffer* existing = search(shapeInfo, stripeIdx);
if (existing != nullptr) {
if (shapeInfoEqual(existing->primary(), shapeInfo)) {
existing->addRef(); // Increment refcount before returning cached buffer
return existing;
}
}
}
// If not found or not matching, grab exclusive lock and try again
SHARED_LOCK_TYPE<MUTEX_TYPE> writeLock(*(*_mutexes)[stripeIdx]);
// Check again under the write lock
ConstantShapeBuffer* existing = search(shapeInfo, stripeIdx);
if (existing != nullptr) {
if (shapeInfoEqual(existing->primary(), shapeInfo)) {
existing->addRef(); // Increment refcount before returning cached buffer
return existing;
}
}
if (_roots == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
// Not found, create a new entry
auto rootsRef = *_roots;
ShapeTrieNode* current = rootsRef[stripeIdx];
if (current == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
if (rank < 0 || rank > SD_MAX_RANK) {
return createFallbackBuffer(shapeInfo, rank);
}
// Safe pointer to track the current node through the insertion process
ShapeTrieNode* safeNodePtr = nullptr;
// Insert rank with signature
safeNodePtr = current->findOrCreateChild(rank, 0, true, shapeSignature);
if (safeNodePtr == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
current = safeNodePtr;
// Insert datatype with signature
safeNodePtr = current->findOrCreateChild(ArrayOptions::dataType(shapeInfo), 1, true, shapeSignature);
if (safeNodePtr == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
current = safeNodePtr;
// Insert order with signature
safeNodePtr = current->findOrCreateChild(shape::order(shapeInfo), 2, true, shapeSignature);
if (safeNodePtr == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
current = safeNodePtr;
// Insert shape values with signature
const LongType* shapeValues = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
safeNodePtr = current->findOrCreateChild(shapeValues[i], 3 + i, true, shapeSignature);
if (safeNodePtr == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
current = safeNodePtr;
}
// Insert stride values with signature
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
safeNodePtr = current->findOrCreateChild(strides[i], 3 + rank + i, false, shapeSignature);
if (safeNodePtr == nullptr) {
return createFallbackBuffer(shapeInfo, rank);
}
current = safeNodePtr;
}
// Check if another thread has already created the buffer
if (ConstantShapeBuffer* nodeBuffer = current->buffer()) {
if (shapeInfoEqual(nodeBuffer->primary(), shapeInfo)) {
nodeBuffer->addRef(); // Increment refcount before returning cached buffer
return nodeBuffer;
}
}
// Create the shape buffer
ConstantShapeBuffer* buffer = ShapeBufferCreatorHelper::getCurrentCreator().create(shapeInfo, rank);
if (buffer == nullptr || buffer->primary() == nullptr) {
// Use fallback if creator fails
if (buffer != nullptr) {
delete buffer; // Clean up invalid buffer
}
return createFallbackBuffer(shapeInfo, rank);
}
// Set the buffer - setBuffer handles ownership properly
current->setBuffer(buffer);
// Return the buffer from the node (could be the one we just set or a pre-existing one)
ConstantShapeBuffer* resultBuffer = current->buffer();
if (resultBuffer == nullptr) {
// Rare case: setBuffer failed to store, return the buffer we created
// Caller owns it with refCount=1 (no addRef needed)
return buffer;
}
// Buffer is now cached, increment refcount for the caller
resultBuffer->addRef();
return resultBuffer;
}
bool DirectShapeTrie::exists(const LongType* shapeInfo) const {
waitForInitialization();
validateShapeInfo(shapeInfo);
size_t stripeIdx = getStripeIndex(shapeInfo);
// Validate stripe index
if (stripeIdx >= NUM_STRIPES) {
return false;
}
// Check if mutex pointer is valid
if (_mutexes == nullptr || (*_mutexes)[stripeIdx] == nullptr) {
return false;
}
int shapeSignature = calculateShapeSignature(shapeInfo);
SHARED_LOCK_TYPE<MUTEX_TYPE> lock(*(*_mutexes)[stripeIdx]);
ConstantShapeBuffer* found = search(shapeInfo, stripeIdx);
return found != nullptr && shapeInfoEqual(found->primary(), shapeInfo);
}
// Original insert method kept for compatibility, but getOrCreate should be used instead
ConstantShapeBuffer* DirectShapeTrie::insert(const LongType* shapeInfo, size_t stripeIdx) {
auto rootsRef = *_roots;
ShapeTrieNode* current = rootsRef[stripeIdx];
const int rank = shape::rank(shapeInfo);
const int shapeSignature = calculateShapeSignature(shapeInfo);
// Insert rank
current = current->findOrCreateChild(rank, 0, true, shapeSignature);
if (!current) {
std::string msg = "Failed to create rank node";
THROW_EXCEPTION(msg.c_str());
return nullptr;
}
// Insert datatype
current = current->findOrCreateChild(ArrayOptions::dataType(shapeInfo), 1, true, shapeSignature);
if (!current) {
std::string msg = "Failed to create datatype node";
THROW_EXCEPTION(msg.c_str());
return nullptr;
}
// Insert order
current = current->findOrCreateChild(shape::order(shapeInfo), 2, true, shapeSignature);
if (!current) {
std::string msg = "Failed to create order node";
THROW_EXCEPTION(msg.c_str());
return nullptr;
}
// Insert shape values
const LongType* shape = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
current = current->findOrCreateChild(shape[i], 3 + i, true, shapeSignature);
if (!current) {
std::string msg = "Failed to create shape value node at index " + std::to_string(i);
THROW_EXCEPTION(msg.c_str());
return nullptr;
}
}
// Insert stride values
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
current = current->findOrCreateChild(strides[i], 3 + rank + i, false, shapeSignature);
if (!current) {
std::string msg = "Failed to create stride value node at index " + std::to_string(i);
THROW_EXCEPTION(msg.c_str());
return nullptr;
}
}
if (!current->buffer()) {
try {
const int shapeInfoLength = shape::shapeInfoLength(rank);
LongType* shapeCopy = new LongType[shapeInfoLength];
std::memcpy(shapeCopy, shapeInfo, shapeInfoLength * sizeof(LongType));
auto deallocator = std::shared_ptr<PrimaryPointerDeallocator>(new PrimaryPointerDeallocator(),
[] (PrimaryPointerDeallocator* ptr) { delete ptr; });
auto hPtr = new PointerWrapper(shapeCopy, deallocator);
auto buffer = new ConstantShapeBuffer(hPtr);
#if defined(SD_GCC_FUNCTRACE)
// Track shape cache allocation
sd::array::ShapeCacheLifecycleTracker::getInstance().recordAllocation(shapeCopy);
#endif
current->setBuffer(buffer);
// Buffer is now cached (trie owns it with refCount=1)
// Increment refcount so caller also has a reference
buffer->addRef();
return buffer;
} catch (const std::exception& e) {
std::string msg = "Shape buffer creation failed: ";
msg += e.what();
THROW_EXCEPTION(msg.c_str());
} catch (...) {
std::string msg = "Shape buffer creation failed with unknown exception";
THROW_EXCEPTION(msg.c_str());
}
}
ConstantShapeBuffer* result = current->buffer();
if (result != nullptr) {
result->addRef(); // Increment refcount before returning cached buffer
}
return result;
}
void DirectShapeTrie::clearCache() {
waitForInitialization();
if (_roots == nullptr || _mutexes == nullptr) {
return;
}
// Clear each stripe
for (size_t i = 0; i < NUM_STRIPES; i++) {
MUTEX_TYPE* mutex = (*_mutexes)[i];
if (mutex == nullptr) continue;
// Lock this stripe
std::lock_guard<MUTEX_TYPE> lock(*mutex);
// Delete the old root node (destructor recursively cleans up all children and buffers)
ShapeTrieNode* oldRoot = (*_roots)[i];
if (oldRoot != nullptr) {
delete oldRoot;
}
// Create a new empty root node
(*_roots)[i] = new ShapeTrieNode(0, 0, false);
}
// Reset current counters (but preserve peak values for diagnostics)
_current_entries.store(0);
_current_bytes.store(0);
}
void DirectShapeTrie::countEntriesAndBytes(const ShapeTrieNode* node, LongType& entries, LongType& bytes) const {
if (node == nullptr) return;
// If this node has a buffer, count it
ConstantShapeBuffer* buffer = node->buffer();
if (buffer != nullptr) {
entries++;
// Calculate buffer size: shapeInfo length is stored at index 0
const LongType* shapeInfo = buffer->primary();
if (shapeInfo != nullptr) {
LongType bufferLength = shape::shapeInfoLength(shapeInfo);
bytes += bufferLength * sizeof(LongType);
}
}
// Recursively count children
const std::vector<ShapeTrieNode*>& children = node->children();
for (const auto* child : children) {
countEntriesAndBytes(child, entries, bytes);
}
}
LongType DirectShapeTrie::getCachedEntries() const {
waitForInitialization();
LongType total_entries = 0;
LongType total_bytes = 0;
if (_roots == nullptr || _mutexes == nullptr) {
return 0;
}
// Count entries across all stripes
for (size_t i = 0; i < NUM_STRIPES; i++) {
MUTEX_TYPE* mutex = (*_mutexes)[i];
if (mutex == nullptr) continue;
// Lock this stripe for reading
std::lock_guard<MUTEX_TYPE> lock(*mutex);
ShapeTrieNode* root = (*_roots)[i];
if (root != nullptr) {
countEntriesAndBytes(root, total_entries, total_bytes);
}
}
// Update current counters
_current_entries.store(total_entries);
_current_bytes.store(total_bytes);
// Update peak if current exceeds it
LongType current_peak = _peak_entries.load();
while (total_entries > current_peak) {
if (_peak_entries.compare_exchange_weak(current_peak, total_entries)) {
break;
}
}
current_peak = _peak_bytes.load();
while (total_bytes > current_peak) {
if (_peak_bytes.compare_exchange_weak(current_peak, total_bytes)) {
break;
}
}
return total_entries;
}
LongType DirectShapeTrie::getCachedBytes() const {
// getCachedEntries() updates both entries and bytes
getCachedEntries();
return _current_bytes.load();
}
LongType DirectShapeTrie::getPeakCachedEntries() const {
return _peak_entries.load();
}
LongType DirectShapeTrie::getPeakCachedBytes() const {
return _peak_bytes.load();
}
void DirectShapeTrie::buildStringRepresentation(const ShapeTrieNode* node, std::stringstream& ss,
const std::string& indent, int currentDepth,
int maxDepth, int& entriesShown, int maxEntries) const {
if (node == nullptr) return;
if (maxDepth != -1 && currentDepth > maxDepth) return;
if (maxEntries != -1 && entriesShown >= maxEntries) return;
// Check if this node has a buffer
ConstantShapeBuffer* buffer = node->buffer();
if (buffer != nullptr) {
const LongType* shapeInfo = buffer->primary();
if (shapeInfo != nullptr) {
entriesShown++;
// Display node info
ss << indent << "Node[level=" << node->level()
<< ", value=" << node->value()
<< ", isShape=" << (node->isShape() ? "true" : "false")
<< "]\n";
// Display shape info details
int rank = shape::rank(shapeInfo);
ss << indent << " Shape: rank=" << rank << ", order=" << shape::order(shapeInfo)
<< ", dtype=" << DataTypeUtils::asString(ArrayOptions::dataType(shapeInfo)) << "\n";
// Display shape dimensions
ss << indent << " Dims: [";
const LongType* dims = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
if (i > 0) ss << ", ";
ss << dims[i];
}
ss << "]\n";
// Display strides
ss << indent << " Strides: [";
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
if (i > 0) ss << ", ";
ss << strides[i];
}
ss << "]\n";
// Display total elements and buffer size
LongType length = shape::length(shapeInfo);
LongType bufferLength = shape::shapeInfoLength(shapeInfo);
ss << indent << " Elements: " << length
<< ", Buffer size: " << (bufferLength * sizeof(LongType)) << " bytes\n";
if (maxEntries != -1 && entriesShown >= maxEntries) {
ss << indent << " ... (max entries reached)\n";
return;
}
}
}
// Recursively process children
const std::vector<ShapeTrieNode*>& children = node->children();
if (!children.empty() && (maxDepth == -1 || currentDepth < maxDepth)) {
for (const auto* child : children) {
if (maxEntries != -1 && entriesShown >= maxEntries) break;
buildStringRepresentation(child, ss, indent + " ", currentDepth + 1,
maxDepth, entriesShown, maxEntries);
}
}
}
std::string DirectShapeTrie::toString(int maxDepth, int maxEntries) const {
waitForInitialization();
std::stringstream ss;
if (_roots == nullptr || _mutexes == nullptr) {
ss << "DirectShapeTrie: [UNINITIALIZED]\n";
return ss.str();
}
// Get current statistics
LongType totalEntries = getCachedEntries();
LongType totalBytes = getCachedBytes();
LongType peakEntries = getPeakCachedEntries();
LongType peakBytes = getPeakCachedBytes();
// Header
ss << "DirectShapeTrie [" << NUM_STRIPES << " stripes]\n";
ss << "Current: " << totalEntries << " entries, " << totalBytes << " bytes\n";
ss << "Peak: " << peakEntries << " entries, " << peakBytes << " bytes\n";
ss << "Showing: max depth=" << (maxDepth == -1 ? "unlimited" : std::to_string(maxDepth))
<< ", max entries=" << (maxEntries == -1 ? "unlimited" : std::to_string(maxEntries)) << "\n";
ss << "---\n";
int entriesShown = 0;
// Traverse each stripe
for (size_t i = 0; i < NUM_STRIPES; i++) {
MUTEX_TYPE* mutex = (*_mutexes)[i];
if (mutex == nullptr) continue;
// Lock this stripe for reading
std::lock_guard<MUTEX_TYPE> lock(*mutex);
ShapeTrieNode* root = (*_roots)[i];
if (root != nullptr && !root->children().empty()) {
ss << "Stripe " << i << ":\n";
buildStringRepresentation(root, ss, " ", 0, maxDepth, entriesShown, maxEntries);
if (maxEntries != -1 && entriesShown >= maxEntries) {
ss << "... (max entries limit reached, " << (totalEntries - entriesShown)
<< " more entries not shown)\n";
break;
}
}
}
if (entriesShown == 0) {
ss << "(Cache is empty)\n";
}
return ss.str();
}
void DirectShapeTrie::getCachedPointers(std::unordered_set<void*>& out_pointers) const {
waitForInitialization();
if (_roots == nullptr || _mutexes == nullptr) {
return;
}
// Traverse all stripes and collect ConstantShapeBuffer pointers
for (size_t i = 0; i < NUM_STRIPES; i++) {
MUTEX_TYPE* mutex = (*_mutexes)[i];
if (mutex == nullptr) continue;
std::lock_guard<MUTEX_TYPE> lock(*mutex);
ShapeTrieNode* root = (*_roots)[i];
if (root != nullptr) {
collectCachedPointers(root, out_pointers);
}
}
}
void DirectShapeTrie::collectCachedPointers(const ShapeTrieNode* node, std::unordered_set<void*>& out_pointers) const {
if (node == nullptr) return;
// If this node has a ConstantShapeBuffer, add its primary pointer (LongType* shape_info) to the set
// This is what ShapeCacheLifecycleTracker uses to track allocations
ConstantShapeBuffer* buffer = node->buffer();
if (buffer != nullptr && buffer->primary() != nullptr) {
out_pointers.insert(buffer->primary());
}
// Recursively collect from all children
for (const auto* child : node->children()) {
collectCachedPointers(child, out_pointers);
}
}
} // namespace sd
@@ -0,0 +1,563 @@
/* ******************************************************************************
*
* 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
******************************************************************************/
#include "../DirectTadTrie.h"
#include <array/TadPack.h>
#include <algorithm>
#include <memory>
#include <atomic>
#include <sstream>
#include <string>
#include <unordered_set>
#include "array/TadCalculator.h"
namespace sd {
std::shared_ptr<TadPack> DirectTadTrie::enhancedSearch(const std::vector<LongType>& dimensions, LongType* originalShape, size_t stripeIdx) {
const TadTrieNode* current = _roots[stripeIdx].get();
int rank = shape::rank(originalShape);
// Navigate to dimension length node
current = findChild(current, dimensions.size(), 0, false, rank);
if (!current) return nullptr;
// Navigate through dimension nodes
for (size_t i = 0; i < dimensions.size(); i++) {
current = findChild(current, dimensions[i], i + 1, true, rank);
if (!current) return nullptr;
}
// Found a matching node, now verify TadPack compatibility
std::shared_ptr<TadPack> pack = current->pack();
if (!pack) return nullptr;
// Use cached signature for fast comparison - no TadCalculator needed!
const TadPackSignature* signature = current->packSignature();
if (!signature) {
// Signature not cached (shouldn't happen, but handle gracefully)
return nullptr;
}
// Fast comparison using cached signature instead of creating TadCalculator
if (!signature->matches(originalShape)) {
return nullptr;
}
return pack;
}
// Enhanced stride-aware hash computation
size_t DirectTadTrie::computeStrideAwareHash(const std::vector<LongType>& dimensions, LongType* originalShape) {
if (!originalShape) return 0;
size_t hash = 17; // Prime number starting point
// Handle empty dimensions specially
if (dimensions.empty()) {
// Empty dimensions case - hash based on shape only
hash = hash * 31 + 0; // Marker for empty dimensions
} else {
// Add dimension-specific hash contribution with position-dependence
for (size_t i = 0; i < dimensions.size(); i++) {
hash = hash * 31 + static_cast<size_t>(dimensions[i]) * (i + 1);
}
}
// Add rank - critical for distinguishing different dimension arrays
int rank = shape::rank(originalShape);
hash = hash * 13 + rank * 19;
// Add shape signature based on shape dimensions with position-dependence
LongType* shapeInfo = shape::shapeOf(originalShape);
for (int i = 0; i < rank; i++) {
hash = hash * 17 + static_cast<size_t>(shapeInfo[i]) * (11 + i);
}
// Add stride information to make the hash more specific
LongType* strides = shape::stride(originalShape);
for (int i = 0; i < rank; i++) {
hash = hash * 23 + static_cast<size_t>(strides[i]) * (7 + i);
}
// Add total element count to distinguish differently sized arrays
hash = hash * 41 + shape::length(originalShape);
// Add data type and order information
hash = hash * 29 + static_cast<size_t>(ArrayOptions::dataType(originalShape));
hash = hash * 37 + static_cast<size_t>(shape::order(originalShape));
// Compute the final stripe index
return hash % NUM_STRIPES;
}
bool DirectTadTrie::exists(const std::vector<LongType>& dimensions, LongType* originalShape) {
if (!originalShape) return false;
const size_t stripeIdx = computeStripeIndex(dimensions, originalShape);
SHARED_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[stripeIdx]);
// Using the enhanced search method which verifies TadPack compatibility
return enhancedSearch(dimensions, originalShape, stripeIdx) != nullptr;
}
std::vector<LongType> DirectTadTrie::sortDimensions(const std::vector<LongType>& dimensions) const {
std::vector<LongType> sorted = dimensions;
std::sort(sorted.begin(), sorted.end());
return sorted;
}
std::shared_ptr<TadPack> DirectTadTrie::search(const std::vector<LongType>& dimensions, int originalShapeRank, size_t stripeIdx) const {
// No need for locking - caller handles locking (e.g., in getOrCreate)
const TadTrieNode* current = _roots[stripeIdx].get();
// First level: dimension length
current = findChild(current, dimensions.size(), 0, false, originalShapeRank);
if (!current) return nullptr;
// Second level: dimensions
for (size_t i = 0; i < dimensions.size(); i++) {
current = findChild(current, dimensions[i], i + 1, true, originalShapeRank);
if (!current) return nullptr;
}
return current->pack();
}
std::shared_ptr<TadPack> DirectTadTrie::getOrCreate(std::vector<LongType>& dimensions, LongType* originalShape) {
if (!originalShape) {
THROW_EXCEPTION("Original shape cannot be null in TAD calculation");
}
// Use the enhanced hash computation for better distribution
const size_t stripeIdx = computeStrideAwareHash(dimensions, originalShape);
// First try a read-only lookup
{
SHARED_LOCK_TYPE<MUTEX_TYPE> readLock(_mutexes[stripeIdx]);
std::shared_ptr<TadPack> existing = enhancedSearch(dimensions, originalShape, stripeIdx);
if (existing) {
return existing;
}
}
// If not found, use insert which will handle the write lock
return insert(dimensions, originalShape);
}
std::shared_ptr<TadPack> DirectTadTrie::insert(std::vector<LongType>& dimensions, LongType* originalShape) {
if (!originalShape) {
THROW_EXCEPTION("Original shape cannot be null in TAD calculation");
}
int rank = shape::rank(originalShape);
// Use the enhanced hash computation for better distribution
const size_t stripeIdx = computeStrideAwareHash(dimensions, originalShape);
// Use exclusive lock for write operation (inserting new TAD packs)
EXCLUSIVE_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[stripeIdx]);
// Check if a compatible TadPack already exists
std::shared_ptr<TadPack> existing = enhancedSearch(dimensions, originalShape, stripeIdx);
if (existing) {
return existing;
}
// No compatible TadPack found, create a new one
TadTrieNode* current = _roots[stripeIdx].get();
// First level: dimension length node with shape rank
current = current->findOrCreateChild(dimensions.size(), 0, false, rank);
if (!current) {
THROW_EXCEPTION("Failed to create dimension length node");
}
// Second level: dimension nodes with shape rank
for (size_t i = 0; i < dimensions.size(); i++) {
current = current->findOrCreateChild(dimensions[i], i + 1, true, rank);
if (!current) {
THROW_EXCEPTION("Failed to create dimension node");
}
}
// Create the TadPack only if it doesn't exist yet
if (!current->pack()) {
TadCalculator *calculator = nullptr;
std::shared_ptr<TadPack> newPack;
try {
calculator = new TadCalculator(originalShape);
calculator->createTadPack(dimensions);
// Create a new TadPack with full dimension information
// Use releaseOffsets() to transfer ownership of the offsets buffer to TadPack
// Wrap in shared_ptr for proper memory management
newPack = std::make_shared<TadPack>(
calculator->tadShape(),
calculator->releaseOffsets(), // Transfer ownership
calculator->numberOfTads(),
dimensions.data(),
dimensions.size());
// Store the TadPack in the node
// setPack now also caches the signature for future fast comparisons
current->setPack(newPack);
// Clean up the calculator (safe now that offsets ownership was transferred)
delete calculator;
calculator = nullptr;
} catch (const std::exception& e) {
// Clean up on exception to prevent memory leaks
// shared_ptr will automatically clean up newPack
if (calculator != nullptr) {
delete calculator;
calculator = nullptr;
}
std::string msg = "TAD creation failed: ";
msg += e.what();
THROW_EXCEPTION(msg.c_str());
}
}
return current->pack();
}
const TadTrieNode* DirectTadTrie::findChild(const TadTrieNode* node, LongType value, int level, bool isDimension, int shapeRank) const {
if (!node) return nullptr;
for (const auto& child : node->children()) {
if (child->value() == value &&
child->level() == level &&
child->isDimension() == isDimension &&
child->shapeRank() == shapeRank) {
return child.get();
}
}
return nullptr;
}
// Helper function to recursively delete TadPacks from a node and its children
// This ensures TadPack destructors are called, which triggers recordDeallocation()
static void deleteTadPacksRecursive(TadTrieNode* node, int& deletedCount) {
if (!node) return;
// First, recursively delete from all children
const auto& children = node->children();
for (const auto& child : children) {
deleteTadPacksRecursive(child.get(), deletedCount);
}
// Then delete this node's TadPack if it exists
// shared_ptr will handle deletion automatically when we reset it
auto pack = node->pack();
if (pack) {
deletedCount++;
// Clear the shared_ptr to trigger TadPack destructor
// The destructor will call TADCacheLifecycleTracker::recordDeallocation()
// if SD_GCC_FUNCTRACE is defined during compilation
node->setPack(nullptr);
}
}
void DirectTadTrie::clear() {
// CRITICAL: Skip cleanup during shutdown to avoid SIGSEGV from corrupted memory
// During JVM/static destruction, memory allocators may have been destroyed,
// leaving corrupted pointers in the trie. Traversing the tree in this state
// causes crashes in deleteTadPacksRecursive.
if (_shutdownInProgress.load(std::memory_order_acquire)) {
return; // Let the OS reclaim memory at exit - this is safe
}
// Clear all stripes
// NOTE: Removed #ifndef __JAVACPP_HACK__ guard to fix TAD cache memory leak
// The guard was preventing cache cleanup when JavaCPP is used (production mode)
// This caused indefinite accumulation of TADPack objects despite clearTADCache() calls
int totalDeleted = 0;
for (size_t i = 0; i < NUM_STRIPES; i++) {
// Use exclusive lock for write operation (clearing the cache)
EXCLUSIVE_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[i]);
// This ensures TadPack destructors are called, which invokes recordDeallocation()
// for proper lifecycle tracking.
//
// IMPORTANT: We CANNOT rely on unique_ptr cascade deletion because:
// 1. TadTrieNode destructor deletes _tadPack only if SD_GCC_FUNCTRACE is defined
// 2. Functrace may be auto-disabled during build, causing guards to evaluate false
// 3. Even if guards pass, destructor might not run if roots are replaced before going out of scope
//
// By explicitly calling deleteTadPacksRecursive() BEFORE replacing roots,
// we guarantee that:
// - All TadPack objects are explicitly deleted via delete operator
// - Their destructors run and call recordDeallocation() (if tracking enabled)
// - Pointers are cleared to nullptr to prevent double-delete in node destructors
int deletedCount = 0;
deleteTadPacksRecursive(_roots[i].get(), deletedCount);
totalDeleted += deletedCount;
// Recreate the root node - this will delete the old tree structure
// (nodes are already cleaned of TadPacks above via deleteTadPacksRecursive)
// The old root's unique_ptr goes out of scope here, triggering node destructor cascade
// But TadPacks are already deleted and nulled out, so no double-delete occurs
_roots[i] = std::make_unique<TadTrieNode>(0, 0, false);
_stripeCounts[i].store(0);
}
// Reset current counters (but preserve peak values for diagnostics)
_current_entries.store(0);
_current_bytes.store(0);
}
void DirectTadTrie::countEntriesAndBytes(const TadTrieNode* node, LongType& entries, LongType& bytes) const {
if (node == nullptr) return;
// If this node has a TadPack, count it
auto pack = node->pack();
if (pack != nullptr) {
entries++;
// Calculate total bytes for this TadPack
// Shape info buffer
const LongType* shapeInfo = pack->primaryShapeInfo();
if (shapeInfo != nullptr) {
LongType shapeInfoLength = shape::shapeInfoLength(shapeInfo);
bytes += shapeInfoLength * sizeof(LongType);
}
// Offsets buffer
const LongType* offsets = pack->primaryOffsets();
if (offsets != nullptr) {
LongType numTads = pack->numberOfTads();
bytes += numTads * sizeof(LongType);
}
}
// Recursively count children
const std::vector<std::unique_ptr<TadTrieNode>>& children = node->children();
for (const auto& child : children) {
countEntriesAndBytes(child.get(), entries, bytes);
}
}
LongType DirectTadTrie::getCachedEntries() const {
LongType total_entries = 0;
LongType total_bytes = 0;
// Count entries across all stripes
for (size_t i = 0; i < NUM_STRIPES; i++) {
// Lock this stripe for reading
SHARED_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[i]);
const TadTrieNode* root = _roots[i].get();
if (root != nullptr) {
countEntriesAndBytes(root, total_entries, total_bytes);
}
}
// Update current counters
_current_entries.store(total_entries);
_current_bytes.store(total_bytes);
// Update peak if current exceeds it
LongType current_peak = _peak_entries.load();
while (total_entries > current_peak) {
if (_peak_entries.compare_exchange_weak(current_peak, total_entries)) {
break;
}
}
current_peak = _peak_bytes.load();
while (total_bytes > current_peak) {
if (_peak_bytes.compare_exchange_weak(current_peak, total_bytes)) {
break;
}
}
return total_entries;
}
LongType DirectTadTrie::getCachedBytes() const {
// getCachedEntries() updates both entries and bytes
getCachedEntries();
return _current_bytes.load();
}
LongType DirectTadTrie::getPeakCachedEntries() const {
return _peak_entries.load();
}
LongType DirectTadTrie::getPeakCachedBytes() const {
return _peak_bytes.load();
}
void DirectTadTrie::buildStringRepresentation(const TadTrieNode* node, std::stringstream& ss,
const std::string& indent, int currentDepth,
int maxDepth, int& entriesShown, int maxEntries) const {
if (node == nullptr) return;
if (maxDepth != -1 && currentDepth > maxDepth) return;
if (maxEntries != -1 && entriesShown >= maxEntries) return;
// Check if this node has a TadPack
auto pack = node->pack();
if (pack != nullptr) {
entriesShown++;
// Display node info
ss << indent << "Node[level=" << node->level()
<< ", value=" << node->value()
<< ", isDim=" << (node->isDimension() ? "true" : "false")
<< ", rank=" << node->shapeRank()
<< "]\n";
// Display TAD pack details
const LongType* shapeInfo = pack->primaryShapeInfo();
if (shapeInfo != nullptr) {
int rank = shape::rank(shapeInfo);
ss << indent << " TAD Shape: rank=" << rank
<< ", order=" << shape::order(shapeInfo)
<< ", dtype=" << DataTypeUtils::asString(ArrayOptions::dataType(shapeInfo)) << "\n";
// Display TAD dimensions
ss << indent << " TAD Dims: [";
const LongType* dims = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
if (i > 0) ss << ", ";
ss << dims[i];
}
ss << "]\n";
// Display TAD strides
ss << indent << " TAD Strides: [";
const LongType* strides = shape::stride(shapeInfo);
for (int i = 0; i < rank; i++) {
if (i > 0) ss << ", ";
ss << strides[i];
}
ss << "]\n";
}
// Display number of TADs and offset info
LongType numTads = pack->numberOfTads();
ss << indent << " Number of TADs: " << numTads << "\n";
// Display memory usage
LongType shapeInfoBytes = 0;
LongType offsetsBytes = 0;
if (shapeInfo != nullptr) {
LongType shapeInfoLength = shape::shapeInfoLength(shapeInfo);
shapeInfoBytes = shapeInfoLength * sizeof(LongType);
}
if (pack->primaryOffsets() != nullptr) {
offsetsBytes = numTads * sizeof(LongType);
}
ss << indent << " Memory: shape_info=" << shapeInfoBytes
<< " bytes, offsets=" << offsetsBytes
<< " bytes, total=" << (shapeInfoBytes + offsetsBytes) << " bytes\n";
if (maxEntries != -1 && entriesShown >= maxEntries) {
ss << indent << " ... (max entries reached)\n";
return;
}
}
// Recursively process children
const std::vector<std::unique_ptr<TadTrieNode>>& children = node->children();
if (!children.empty() && (maxDepth == -1 || currentDepth < maxDepth)) {
for (const auto& child : children) {
if (maxEntries != -1 && entriesShown >= maxEntries) break;
buildStringRepresentation(child.get(), ss, indent + " ", currentDepth + 1,
maxDepth, entriesShown, maxEntries);
}
}
}
std::string DirectTadTrie::toString(int maxDepth, int maxEntries) const {
std::stringstream ss;
// Get current statistics
LongType totalEntries = getCachedEntries();
LongType totalBytes = getCachedBytes();
LongType peakEntries = getPeakCachedEntries();
LongType peakBytes = getPeakCachedBytes();
// Header
ss << "DirectTadTrie [" << NUM_STRIPES << " stripes]\n";
ss << "Current: " << totalEntries << " entries, " << totalBytes << " bytes\n";
ss << "Peak: " << peakEntries << " entries, " << peakBytes << " bytes\n";
ss << "Showing: max depth=" << (maxDepth == -1 ? "unlimited" : std::to_string(maxDepth))
<< ", max entries=" << (maxEntries == -1 ? "unlimited" : std::to_string(maxEntries)) << "\n";
ss << "---\n";
int entriesShown = 0;
// Traverse each stripe
for (size_t i = 0; i < NUM_STRIPES; i++) {
// Lock this stripe for reading
SHARED_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[i]);
const TadTrieNode* root = _roots[i].get();
if (root != nullptr && !root->children().empty()) {
ss << "Stripe " << i << ":\n";
buildStringRepresentation(root, ss, " ", 0, maxDepth, entriesShown, maxEntries);
if (maxEntries != -1 && entriesShown >= maxEntries) {
ss << "... (max entries limit reached, " << (totalEntries - entriesShown)
<< " more entries not shown)\n";
break;
}
}
}
if (entriesShown == 0) {
ss << "(Cache is empty)\n";
}
return ss.str();
}
void DirectTadTrie::getCachedPointers(std::unordered_set<void*>& out_pointers) const {
// Traverse all stripes and collect TadPack pointers
for (size_t i = 0; i < NUM_STRIPES; i++) {
SHARED_LOCK_TYPE<MUTEX_TYPE> lock(_mutexes[i]);
const TadTrieNode* root = _roots[i].get();
if (root != nullptr) {
collectCachedPointers(root, out_pointers);
}
}
}
void DirectTadTrie::collectCachedPointers(const TadTrieNode* node, std::unordered_set<void*>& out_pointers) const {
if (node == nullptr) return;
// If this node has a TadPack, add it to the set
auto pack = node->pack();
if (pack != nullptr) {
out_pointers.insert(pack.get());
}
// Recursively collect from all children
for (const auto& child : node->children()) {
collectCachedPointers(child.get(), out_pointers);
}
}
} // namespace sd
@@ -0,0 +1,368 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/EigenValsAndVecs.h>
#include <helpers/HessenbergAndSchur.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
EigenValsAndVecs<T>::EigenValsAndVecs(NDArray& matrix)
: _Vals(matrix.dataType(), matrix.getContext(), true),
_Vecs(matrix.dataType(), matrix.getContext(), true) {
if (matrix.rankOf() != 2)
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs constructor: input matrix must be 2D !");
if (matrix.sizeAt(0) != matrix.sizeAt(1))
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs constructor: input array must be 2D square matrix !");
Schur<T> schur(matrix);
NDArray* schurMatrixU = schur.u;
NDArray* schurMatrixT = schur.t;
std::vector<LongType> shape = {schurMatrixU->sizeAt(1), schurMatrixU->sizeAt(1), 2};
_Vecs = NDArray(matrix.ordering(), shape, matrix.dataType(),
matrix.getContext());
std::vector<LongType> shape2 = {matrix.sizeAt(1), 2};
_Vals = NDArray(matrix.ordering(), shape2, matrix.dataType(), matrix.getContext());
// sequence of methods calls matters
calcEigenVals(*schurMatrixT);
calcPseudoEigenVecs(*schurMatrixT, *schurMatrixU); // pseudo-eigenvectors are real and will be stored in schurMatrixU
calcEigenVecs(*schurMatrixU);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcEigenVals_(NDArray& schurMatrixT, NDArray& _Vals) {
const int numOfCols = schurMatrixT.sizeAt(1);
// calculate eigenvalues _Vals
int i = 0;
while (i < numOfCols) {
if (i == numOfCols - 1 || schurMatrixT.t<T>(i + 1, i) == T(0.f)) {
_Vals.r<T>(i, 0) = schurMatrixT.t<T>(i, i); // real part
_Vals.r<T>(i, 1) = T(0); // imaginary part
if (!math::sd_isfin<T>(_Vals.t<T>(static_cast<T>(i), static_cast<T>(0)))) {
THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !");
return;
}
++i;
} else {
T p = T(0.5) * (schurMatrixT.t<T>(i, i) - schurMatrixT.t<T>(i + 1, i + 1));
T z;
{
T t0 = schurMatrixT.t<T>(i + 1, i);
T t1 = schurMatrixT.t<T>(i, i + 1);
T maxval = math::sd_max<T>(math::sd_abs<T,T>(p), math::sd_max<T>(math::sd_abs<T,T>(t0), math::sd_abs<T,T>(t1)));
t0 /= maxval;
t1 /= maxval;
T p0 = p / maxval;
z = maxval * math::sd_sqrt<T, T>(math::sd_abs<T,T>(p0 * p0 + t0 * t1));
}
_Vals.r<T>(i, 0) = _Vals.r<T>(i + 1, 0) = schurMatrixT.t<T>(i + 1, i + 1) + p;
_Vals.r<T>(i, 1) = z;
_Vals.r<T>(i + 1, 1) = -z;
if (!(math::sd_isfin<T>(_Vals.t<T>(i, 0)) && math::sd_isfin<T>(_Vals.t<T>(i + 1, 0)) &&
math::sd_isfin<T>(_Vals.t<T>(i, 1))) &&
math::sd_isfin<T>(_Vals.t<T>(i + 1, 1))) {
THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !");
return;
}
i += 2;
}
}
}
template <typename T>
void EigenValsAndVecs<T>::calcEigenVals(NDArray& schurMatrixT) {
calcEigenVals_<T>(schurMatrixT, _Vals);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcPseudoEigenVecs_(NDArray& schurMatrixT, NDArray& schurMatrixU, NDArray& _Vals) {
const int numOfCols = schurMatrixU.sizeAt(1);
T norm = static_cast<T>(0);
for (int j = 0; j < numOfCols; ++j) {
NDArray *viewPtr = schurMatrixT({j, j + 1, math::sd_max<LongType>(j - 1, 0), numOfCols});
auto* reduceResult = viewPtr->reduceNumber(reduce::ASum);
norm += reduceResult->template t<T>(0);
delete reduceResult;
delete viewPtr;
}
if (norm == T(0)) return;
for (int n = numOfCols - 1; n >= 0; n--) {
T p = _Vals.t<T>(n, 0); // real part
T q = _Vals.t<T>(n, 1); // imaginary part
if (q == (T)0) { // not complex
T lastr((T)0), lastw((T)0);
int l = n;
schurMatrixT.r<T>(n, n) = T(1);
for (int i = n - 1; i >= 0; i--) {
T w = schurMatrixT.t<T>(i, i) - p;
NDArray *view1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *view2Ptr = schurMatrixT({l, n + 1, n, n + 1}, true);
NDArray *dotResult = mmul(*view1Ptr, *view2Ptr);
T r = dotResult->template t<T>(0);
delete view1Ptr;
delete view2Ptr;
delete dotResult;
if (_Vals.t<T>(i, 1) < T(0)) {
lastw = w;
lastr = r;
} else {
l = i;
if (_Vals.t<T>(i, 1) == T(0)) {
if (w != T(0))
schurMatrixT.r<T>(i, n) = -r / w;
else
schurMatrixT.r<T>(i, n) = -r / (DataTypeUtils::eps<T>() * norm);
} else {
T x = schurMatrixT.t<T>(i, i + 1);
T y = schurMatrixT.t<T>(i + 1, i);
T denom = (_Vals.t<T>(i, 0) - p) * (_Vals.t<T>(i, 0) - p) + _Vals.t<T>(i, 1) * _Vals.t<T>(i, 1);
T t = (x * lastr - lastw * r) / denom;
schurMatrixT.r<T>(i, n) = t;
if (math::sd_abs<T,T>(x) > math::sd_abs<T,T>(lastw))
schurMatrixT.r<T>(i + 1, n) = (-r - w * t) / x;
else
schurMatrixT.r<T>(i + 1, n) = (-lastr - y * t) / lastw;
}
T t = math::sd_abs<T,T>(schurMatrixT.t<T>(i, n));
if ((DataTypeUtils::eps<T>() * t) * t > T(1)) {
NDArray *divViewPtr = schurMatrixT({schurMatrixT.sizeAt(0) - numOfCols + i, -1, n, n + 1});
*divViewPtr /= t;
delete divViewPtr;
}
}
}
} else if (q < T(0) && n > 0) { // complex
T lastra(0), lastsa(0), lastw(0);
int l = n - 1;
if (math::sd_abs<T,T>(schurMatrixT.t<T>(n, n - 1)) > math::sd_abs<T,T>(schurMatrixT.t<T>(n - 1, n))) {
schurMatrixT.r<T>(n - 1, n - 1) = q / schurMatrixT.t<T>(n, n - 1);
schurMatrixT.r<T>(n - 1, n) = -(schurMatrixT.t<T>(n, n) - p) / schurMatrixT.t<T>(n, n - 1);
} else {
EigenValsAndVecs<T>::divideComplexNums(T(0), -schurMatrixT.t<T>(n - 1, n), schurMatrixT.t<T>(n - 1, n - 1) - p,
q, schurMatrixT.r<T>(n - 1, n - 1), schurMatrixT.r<T>(n - 1, n));
}
schurMatrixT.r<T>(n, n - 1) = T(0);
schurMatrixT.r<T>(n, n) = T(1);
for (int i = n - 2; i >= 0; i--) {
NDArray *raView1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *raView2Ptr = schurMatrixT({l, n + 1, n - 1, n}, true);
NDArray *raDotResult = mmul(*raView1Ptr, *raView2Ptr);
T ra = raDotResult->template t<T>(0);
delete raView1Ptr;
delete raView2Ptr;
delete raDotResult;
NDArray *saView1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *saView2Ptr = schurMatrixT({l, n + 1, n, n + 1}, true);
NDArray *saDotResult = mmul(*saView1Ptr, *saView2Ptr);
T sa = saDotResult->template t<T>(0);
delete saView1Ptr;
delete saView2Ptr;
delete saDotResult;
T w = schurMatrixT.t<T>(i, i) - p;
if (_Vals.t<T>(i, 1) < T(0)) {
lastw = w;
lastra = ra;
lastsa = sa;
} else {
l = i;
if (_Vals.t<T>(i, 1) == T(0)) {
EigenValsAndVecs<T>::divideComplexNums(-ra, -sa, w, q, schurMatrixT.r<T>(i, n - 1),
schurMatrixT.r<T>(i, n));
} else {
T x = schurMatrixT.t<T>(i, i + 1);
T y = schurMatrixT.t<T>(i + 1, i);
T vr = (_Vals.t<T>(i, 0) - p) * (_Vals.t<T>(i, 0) - p) + _Vals.t<T>(i, 1) * _Vals.t<T>(i, 1) - q * q;
T vi = (_Vals.t<T>(i, 0) - p) * T(2) * q;
if ((vr == T(0)) && (vi == T(0)))
vr = DataTypeUtils::eps<T>() * norm *
(math::sd_abs<T,T>(w) + math::sd_abs<T,T>(q) + math::sd_abs<T,T>(x) + math::sd_abs<T,T>(y) +
math::sd_abs<T,T>(lastw));
EigenValsAndVecs<T>::divideComplexNums(x * lastra - lastw * ra + q * sa, x * lastsa - lastw * sa - q * ra,
vr, vi, schurMatrixT.r<T>(i, n - 1), schurMatrixT.r<T>(i, n));
if (math::sd_abs<T,T>(x) > (math::sd_abs<T,T>(lastw) + math::sd_abs<T,T>(q))) {
schurMatrixT.r<T>(i + 1, n - 1) =
(-ra - w * schurMatrixT.t<T>(i, n - 1) + q * schurMatrixT.t<T>(i, n)) / x;
schurMatrixT.r<T>(i + 1, n) = (-sa - w * schurMatrixT.t<T>(i, n) - q * schurMatrixT.t<T>(i, n - 1)) / x;
} else
EigenValsAndVecs<T>::divideComplexNums(-lastra - y * schurMatrixT.t<T>(i, n - 1),
-lastsa - y * schurMatrixT.t<T>(i, n), lastw, q,
schurMatrixT.r<T>(i + 1, n - 1), schurMatrixT.r<T>(i + 1, n));
}
T t = math::sd_max<T>(math::sd_abs<T,T>(schurMatrixT.t<T>(i, n - 1)), math::sd_abs<T,T>(schurMatrixT.t<T>(i, n)));
if ((DataTypeUtils::eps<T>() * t) * t > T(1)) {
NDArray *divViewPtr = schurMatrixT({i, numOfCols, n - 1, n + 1});
*divViewPtr /= t;
delete divViewPtr;
}
}
}
n--;
} else
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs::calcEigenVecs: internal bug !");
}
for (int j = numOfCols - 1; j >= 0; j--) {
NDArray *uViewPtr = schurMatrixU({0, 0, 0, j + 1}, true);
NDArray *tViewPtr = schurMatrixT({0, j + 1, j, j + 1}, true);
NDArray *assignResult = mmul(*uViewPtr, *tViewPtr);
delete uViewPtr;
delete tViewPtr;
NDArray *uAssignPtr = schurMatrixU({0, 0, j, j + 1}, true);
uAssignPtr->assign(assignResult);
delete uAssignPtr;
delete assignResult;
}
}
template <typename T>
void EigenValsAndVecs<T>::calcPseudoEigenVecs(NDArray& schurMatrixT, NDArray& schurMatrixU) {
calcPseudoEigenVecs_<T>(schurMatrixT, schurMatrixU, _Vals);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcEigenVecs_(NDArray& schurMatrixU, NDArray& _Vals, NDArray& _Vecs) {
const T precision = T(2) * DataTypeUtils::eps<T>();
const int numOfCols = schurMatrixU.sizeAt(1);
for (int j = 0; j < numOfCols; ++j) {
if (math::sd_abs<T,T>(_Vals.t<T>(j, 1)) <= math::sd_abs<T,T>(_Vals.t<T>(j, 0)) * precision ||
j + 1 == numOfCols) { // real
_Vecs.syncToDevice();
NDArray *assignPtr = schurMatrixU({0, 0, j, j + 1});
NDArray *vecsViewPtr = _Vecs({0, 0, j, j + 1, 0, 1});
vecsViewPtr->assign(assignPtr);
delete assignPtr;
delete vecsViewPtr;
NDArray *vecsView2Ptr = _Vecs({0, 0, j, j + 1, 1, 2});
*vecsView2Ptr = (T)0;
delete vecsView2Ptr;
// normalize
NDArray *norm2ViewPtr = _Vecs({0, 0, j, j + 1, 0, 1});
auto* norm2Result = norm2ViewPtr->reduceNumber(reduce::SquaredNorm);
const T norm2 = norm2Result->template t<T>(0);
delete norm2Result;
if (norm2 > (T)0) *norm2ViewPtr /= math::sd_sqrt<T, T>(norm2);
delete norm2ViewPtr;
} else { // complex
for (int i = 0; i < numOfCols; ++i) {
_Vecs.r<T>(i, j, 0) = _Vecs.r<T>(i, j + 1, 0) = schurMatrixU.t<T>(i, j);
_Vecs.r<T>(i, j, 1) = schurMatrixU.t<T>(i, j + 1);
_Vecs.r<T>(i, j + 1, 1) = -schurMatrixU.t<T>(i, j + 1);
}
// normalize
NDArray *norm2View1Ptr = _Vecs({0, 0, j, j + 1, 0, 0});
auto* norm2Result1 = norm2View1Ptr->reduceNumber(reduce::SquaredNorm);
T norm2 = norm2Result1->template t<T>(0);
delete norm2Result1;
if (norm2 > (T)0) *norm2View1Ptr /= math::sd_sqrt<T, T>(norm2);
delete norm2View1Ptr;
// normalize
NDArray *norm2View2Ptr = _Vecs({0, 0, j + 1, j + 2, 0, 0});
auto* norm2Result2 = norm2View2Ptr->reduceNumber(reduce::SquaredNorm);
norm2 = norm2Result2->template t<T>(0);
delete norm2Result2;
if (norm2 > (T)0) *norm2View2Ptr /= math::sd_sqrt<T, T>(norm2);
delete norm2View2Ptr;
++j;
}
}
}
template <typename T>
void EigenValsAndVecs<T>::calcEigenVecs(NDArray& schurMatrixU) {
calcEigenVecs_<T>(schurMatrixU, _Vals, _Vecs);
}
template <typename T>
void eig_(NDArray& input, NDArray& vals, NDArray& vecs) {
assert(input.rankOf() == 2 && "input is not a matrix");
assert(input.sizeAt(0) == input.sizeAt(1) && "input is not a square matrix");
assert(vals.rankOf() == 2 && vals.sizeAt(0) == input.sizeAt(0) && vals.sizeAt(1) == 2 &&
"incorrect shape for the eigenvalue results vals");
assert(vecs.rankOf() == 3 && vecs.sizeAt(0) == input.sizeAt(0) && vecs.sizeAt(1) == input.sizeAt(0) &&
vecs.sizeAt(2) == 2 && "incorrect shape for the eigenvector results vecs");
Schur<T> schur(input);
NDArray* schurMatrixU = schur.u;
NDArray* schurMatrixT = schur.t;
calcEigenVals_<T>(*schurMatrixT, vals);
calcPseudoEigenVecs_<T>(*schurMatrixT, *schurMatrixU, vals);
calcEigenVecs_<T>(*schurMatrixU, vals, vecs);
}
void eig(NDArray& input, NDArray& vals, NDArray& vecs) {
BUILD_SINGLE_SELECTOR(input.dataType(), eig_, (input, vals, vecs), SD_FLOAT_TYPES);
}
BUILD_SINGLE_TEMPLATE( void eig_, (NDArray& input, NDArray& vals, NDArray& vecs), SD_FLOAT_TYPES);
BUILD_SINGLE_TEMPLATE( class EigenValsAndVecs, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
+114
View File
@@ -0,0 +1,114 @@
/* ******************************************************************************
*
*
* 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
//
#include <graph/VariableType.h>
#include <helpers/EnumUtils.h>
using namespace sd::graph;
namespace sd {
const char* EnumUtils::_VariableTypeToString(VariableType variableType) {
switch (variableType) {
case NDARRAY:
return "NDARRAY";
case ARRAY_LIST:
return "ARRAY_LIST";
case FLOW:
return "FLOW";
default:
return "UNKNOWN VariableType";
}
}
const char* EnumUtils::_OpTypeToString(::graph::OpType opType) {
switch (opType) {
case ::graph::OpType_REDUCE_SAME:
return "REDUCE_SAME";
case ::graph::OpType_REDUCE_BOOL:
return "REDUCE_BOOL";
case ::graph::OpType_REDUCE_LONG:
return "REDUCE_LONG";
case ::graph::OpType_REDUCE_FLOAT:
return "REDUCE_FLOAT";
case ::graph::OpType_BOOLEAN:
return "BOOLEAN";
case ::graph::OpType_BROADCAST:
return "BROADCAST";
case ::graph::OpType_BROADCAST_BOOL:
return "BROADCAST_BOOL";
case ::graph::OpType_PAIRWISE:
return "PAIRWISE";
case ::graph::OpType_PAIRWISE_BOOL:
return "PAIRWISE_BOOL";
case ::graph::OpType_CUSTOM:
return "CUSTOM";
case ::graph::OpType_LOGIC:
return "LOGIC";
case ::graph::OpType_TRANSFORM_SAME:
return "TRANSFORM_SAME";
case ::graph::OpType_TRANSFORM_FLOAT:
return "TRANSFORM_FLOAT";
case ::graph::OpType_TRANSFORM_BOOL:
return "TRANSFORM_BOOL";
case ::graph::OpType_TRANSFORM_STRICT:
return "TRANSFORM_STRICT";
case ::graph::OpType_TRANSFORM_ANY:
return "TRANSFORM_ANY";
case ::graph::OpType_INDEX_REDUCE:
return "INDEX_ACCUMULATION";
case ::graph::OpType_SCALAR:
return "SCALAR";
case ::graph::OpType_SCALAR_BOOL:
return "SCALAR_BOOL";
case ::graph::OpType_SHAPE:
return "SHAPE";
default:
return "UNKNOWN OpType";
}
}
const char* EnumUtils::_LogicOpToString(int opNum) {
switch (opNum) {
case 0:
return "WHILE";
case 10:
return "SCOPE";
case 20:
return "CONDITIONAL";
case 30:
return "SWITCH";
case 40:
return "RETURN";
case 60:
return "MERGE";
case 70:
return "LOOP_COND";
case 80:
return "NEXT_ITERATION";
case 90:
return "EXIT";
case 100:
return "ENTER";
default:
return "UNKNOWN OPERATION";
}
}
} // namespace sd
+226
View File
@@ -0,0 +1,226 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/FullPivLU.h>
#include <ops/declarable/helpers/triangular_solve.h>
#include <numeric>
#if NOT_EXCLUDED(OP_triangular_solve)
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
// A{M,K} * x{K,N} = b{M,N}
template <typename T>
void FullPivLU<T>::solve(NDArray &A, NDArray &b, NDArray& x) {
if (A.rankOf() != 2) THROW_EXCEPTION("FullPivLU::solve: input matrix A must be 2D !");
if (A.sizeAt(0) != b.sizeAt(0))
THROW_EXCEPTION("FullPivLU::solve: A and b must have the same number of rows !");
if (A.sizeAt(1) != x.sizeAt(0))
THROW_EXCEPTION("FullPivLU::solve: number of A columns must be equal to number of x rows !");
NDArray *LU = A.dup(A.ordering());
NDArray luRef = *LU;
const int rows = LU->sizeAt(0);
const int cols = LU->sizeAt(1);
const int diagLen = math::sd_min<int>(rows, cols);
std::vector<int> rowsInds(rows), colsInds(cols);
int nonZeroPivots1 = diagLen;
T maxPivot = T(0);
for (int k = 0; k < diagLen; ++k) {
NDArray *bottomRightCornerPtr = luRef({k, rows, k, cols}, true);
NDArray bottomRightCorner = *bottomRightCornerPtr;
delete bottomRightCornerPtr;
NDArray *indexNum = bottomRightCorner.indexReduceNumber(indexreduce::IndexAbsoluteMax);
const int indPivot = static_cast<int>(indexNum->t<LongType>(0));
int colPivot = indPivot % (cols - k);
int rowPivot = indPivot / (cols - k);
T currentMax = math::sd_abs<T,T>(bottomRightCorner.t<T>(rowPivot, colPivot));
// take into account that this was calculated in corner, not in whole LU
rowPivot += k;
colPivot += k;
if (currentMax == T(0)) {
nonZeroPivots1 = k;
for (int i = k; i < diagLen; ++i) rowsInds[i] = colsInds[i] = i;
delete indexNum;
break;
}
if (currentMax > maxPivot) maxPivot = currentMax;
rowsInds[k] = rowPivot;
colsInds[k] = colPivot;
if (k != rowPivot) {
NDArray *row1Ptr = luRef({k, k + 1, 0, 0}, true);
NDArray *row2Ptr = luRef({rowPivot, rowPivot + 1, 0, 0}, true);
row1Ptr->swapUnsafe(*row2Ptr);
delete row1Ptr;
delete row2Ptr;
}
if (k != colPivot) {
NDArray *col1Ptr = luRef({0, 0, k, k + 1}, true);
NDArray *col2Ptr = luRef({0, 0, colPivot, colPivot + 1}, true);
col1Ptr->swapUnsafe(*col2Ptr);
delete col1Ptr;
delete col2Ptr;
}
if (k < rows - 1) {
NDArray *divViewPtr = luRef({k + 1, rows, k, k + 1}, true);
*divViewPtr /= luRef.t<T>(k, k);
delete divViewPtr;
}
if (k < diagLen - 1) {
NDArray *leftPtr = luRef({k + 1, rows, k, k + 1}, true);
NDArray *rightPtr = luRef({k, k + 1, k + 1, cols}, true);
NDArray *targetPtr = luRef({k + 1, rows, k + 1, cols}, true);
NDArray left = *leftPtr;
NDArray right = *rightPtr;
NDArray *mulResult = mmul(left, right);
*targetPtr -= *mulResult;
delete mulResult;
delete leftPtr;
delete rightPtr;
delete targetPtr;
}
delete indexNum;
}
//***************************************************//
const T threshold = maxPivot * DataTypeUtils::eps<T>() * (T)diagLen;
int nonZeroPivots2 = 0;
for (int i = 0; i < nonZeroPivots1; ++i)
nonZeroPivots2 += static_cast<int>(math::sd_abs<T,T>(luRef.t<T>(i, i)) > threshold);
if (nonZeroPivots2 == 0) {
x.nullify();
delete LU;
return;
}
//***************************************************//
std::vector<int> rowsPermut1(rows), rowsPermut2(rows), colsPermut(cols);
std::iota(rowsPermut1.begin(), rowsPermut1.end(), 0);
std::iota(colsPermut.begin(), colsPermut.end(), 0);
for (int k = diagLen - 1; k >= 0; --k) math::sd_swap<int>(rowsPermut1[k], rowsPermut1[rowsInds[k]]);
for (int k = 0; k < diagLen; ++k) math::sd_swap<int>(colsPermut[k], colsPermut[colsInds[k]]);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < rows; ++j)
if (i == rowsPermut1[j]) {
rowsPermut2[i] = j;
break;
}
//***************************************************//
NDArray *bUlike = b.ulike();
NDArray c = *bUlike;
for (int i = 0; i < rows; ++i) {
NDArray *cAssignPtr = b({rowsPermut2[i], rowsPermut2[i] + 1, 0, 0}, true);
NDArray cAssign = *cAssignPtr;
delete cAssignPtr;
NDArray *cTargetPtr = c({i, i + 1, 0, 0}, true);
cTargetPtr->assign(&cAssign);
delete cTargetPtr;
}
NDArray *cTopRows1Ptr = c({0, diagLen, 0, 0}, true);
NDArray cTopRows1 = *cTopRows1Ptr;
delete cTopRows1Ptr;
NDArray *luDiagPtr = luRef({0, diagLen, 0, diagLen}, true);
// TriangularSolver<T>::solve(LU({0,diagLen, 0,diagLen}, true), cTopRows1, true, true, cTopRows1);
helpers::triangularSolve2D<T>(nullptr, *luDiagPtr, cTopRows1, true, true, cTopRows1);
delete luDiagPtr;
if (rows > cols) {
NDArray *leftPtr = luRef({cols, -1, 0, 0}, true);
NDArray *rightPtr = c({0, cols, 0, 0}, true);
NDArray *targetPtr = c({cols, -1, 0, 0}, true);
NDArray left = *leftPtr;
NDArray right = *rightPtr;
NDArray *mulResult = mmul(left, right);
*targetPtr -= *mulResult;
delete mulResult;
delete leftPtr;
delete rightPtr;
delete targetPtr;
}
NDArray *cTopRows2Ptr = c({0, nonZeroPivots2, 0, 0}, true);
NDArray cTopRows2 = *cTopRows2Ptr;
delete cTopRows2Ptr;
NDArray *luNonZeroPtr = luRef({0, nonZeroPivots2, 0, nonZeroPivots2}, true);
helpers::triangularSolve2D<T>(nullptr, *luNonZeroPtr, cTopRows2, false, false, cTopRows2);
delete luNonZeroPtr;
for (int i = 0; i < nonZeroPivots2; ++i) {
NDArray *cAssignPtr = c({i, i + 1, 0, 0}, true);
NDArray cAssign = *cAssignPtr;
delete cAssignPtr;
NDArray *xTargetPtr = x({colsPermut[i], colsPermut[i] + 1, 0, 0}, true);
xTargetPtr->assign(&cAssign);
delete xTargetPtr;
}
for (int i = nonZeroPivots2; i < cols; ++i) {
NDArray *xNullifyPtr = x({colsPermut[i], colsPermut[i] + 1, 0, 0}, true);
xNullifyPtr->nullify();
delete xNullifyPtr;
}
delete LU;
delete bUlike;
}
BUILD_SINGLE_TEMPLATE( class FullPivLU, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
#endif
+151
View File
@@ -0,0 +1,151 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com), created on 16.07.2018
//
#include <array/NDArrayFactory.h>
#include <helpers/GradCheck.h>
namespace sd {
//////////////////////////////////////////////////////////////////////////
void GradCheck::fillGradArrays(const LossFunc loss, const std::vector<NDArray*>& gradArrs) {
const int numInGradArrs = gradArrs.size();
// fill input gradient arrays in accordance to type of loss function
switch (loss) {
case MEAN:
for (int i = 0; i < numInGradArrs; ++i) *gradArrs[i] = 1. / gradArrs[i]->lengthOf();
break;
case SUM:
for (int i = 0; i < numInGradArrs; ++i) *gradArrs[i] = 1.;
break;
default:
THROW_EXCEPTION("GradCheck::fillGradArrays: invalid type of loss function !");
}
}
//////////////////////////////////////////////////////////////////////////
bool GradCheck::checkGrad(ops::DeclarableOp& opFF, ops::DeclarableOp& opBP, const OpArgsHolder& argsHolderFF,
const OpArgsHolder& argsHolderBP, const std::vector<bool>& whatArrsToCheck,
const std::vector<double>& idxRange, const LossFunc loss) {
const int numInArrsFF =
argsHolderFF.getNumInArrs(); // at the same time numInArrsFF = number of output arrays in opBP
const int numInGradArrsBP =
argsHolderBP.getNumInArrs() - numInArrsFF; // because argsHolderBP.getNumInArrs() = numInArrsFF + numInGradArrsBP
const std::vector<NDArray*>& inArrsFF = argsHolderFF.getInArrs();
const std::vector<NDArray*>& inArrsBP = argsHolderBP.getInArrs();
// fill input gradient arrays in accordance to kind of loss function
fillGradArrays(loss, std::vector<NDArray*>(&inArrsBP[numInArrsFF], &inArrsBP[numInArrsFF + numInGradArrsBP]));
// back prop pass
ResultSet outArrsBP = opBP.execute(argsHolderBP); // number of output arrays in back prop = numInArrsFF;
NDArray tmpScalar(DOUBLE, inArrsFF[0]->getContext()); // scalar = 0
for (int i = 0; i < numInArrsFF; ++i) { // loop through input array
if (!whatArrsToCheck.empty() && static_cast<bool>(whatArrsToCheck[i]) == false) continue;
const LongType idxStart = static_cast<LongType>(idxRange[0] * inArrsFF[i]->lengthOf());
const LongType idxEnd = static_cast<LongType>(idxRange[1] * inArrsFF[i]->lengthOf());
for (LongType j = idxStart; j < idxEnd; ++j) { // loop through all elements for current array
const double orig = inArrsFF[i]->e<double>(j);
// add epsilon, feed forward
inArrsFF[i]->p<double>(j, orig + EPSILON);
ResultSet outArrsFF = opFF.execute(argsHolderFF);
int numOutArrs = outArrsFF.size();
double scorePlus = 0.;
for (int k = 0; k < numOutArrs; ++k) { // loop through output arrays
if (loss == SUM)
outArrsFF.at(k)->reduceNumber(reduce::Sum, &tmpScalar);
else
outArrsFF.at(k)->reduceNumber(reduce::Mean, &tmpScalar);
scorePlus += tmpScalar.e<double>(0);
}
// subtract epsilon, feed forward
inArrsFF[i]->p<double>(j, orig - EPSILON);
outArrsFF = opFF.execute(argsHolderFF);
double scoreMinus = 0.;
for (int k = 0; k < numOutArrs; ++k) { // loop through output arrays
if (loss == SUM)
outArrsFF.at(k)->reduceNumber(reduce::Sum, &tmpScalar);
else
outArrsFF.at(k)->reduceNumber(reduce::Mean, &tmpScalar);
scoreMinus += tmpScalar.e<double>(0);
}
// restore initial element value
inArrsFF[i]->p<double>(j, orig);
// calculate numerical gradient
const double numericalGrad = (scorePlus - scoreMinus) / (2 * EPSILON);
if (std::isnan(numericalGrad) || std::isinf(numericalGrad)) {
printf(
"GradCheck::checkGrad: got wrong value for numerical gradient for input array # %i and its element at "
"position %lld ! \n",
i, j);
THROW_EXCEPTION("");
}
// get analytical gradient
const double analyticGrad = outArrsBP.at(i)->e<double>(j);
if (std::isnan(analyticGrad) || std::isinf(analyticGrad)) {
printf(
"GradCheck::checkGrad: got wrong value for analytical gradient for input array # %i and its element at "
"position %lld ! \n",
i, j);
THROW_EXCEPTION("");
}
// calculate relative error
double relError;
if (numericalGrad == 0. && analyticGrad == 0.)
relError = 0.;
else
relError = math::sd_abs<double,double>(analyticGrad - numericalGrad) /
(math::sd_abs<double,double>(analyticGrad) + math::sd_abs<double,double>(numericalGrad));
// verify result
if (relError > MAXRELERR || std::isnan(relError)) {
if (math::sd_abs<double,double>(analyticGrad - numericalGrad) < MINABSERR) continue;
printf("numericalGrad = %.15f, analyticGrad = %.15f \n", numericalGrad, analyticGrad);
printf(
"GradCheck::checkGrad: got RELERROR = %f > MAXRELERROR(%f) for input array # %i and its element at "
"position %lld ! \n",
relError, MAXRELERR, i, j);
return false;
}
}
}
return true;
}
} // namespace sd
@@ -0,0 +1,405 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/HessenbergAndSchur.h>
#include <helpers/hhSequence.h>
#include <helpers/householder.h>
#include <helpers/jacobiSVD.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
Hessenberg<T>::Hessenberg(NDArray* matrix) {
if (matrix->rankOf() != 2) THROW_EXCEPTION("ops::helpers::Hessenberg constructor: input matrix must be 2D !");
if (matrix->sizeAt(0) == 1) {
std::vector<LongType> qShape = {1, 1};
_Q = new NDArray(matrix->ordering(),qShape, matrix->dataType(), matrix->getContext());
*_Q = 1;
_H = matrix->dup(matrix->ordering());
return;
}
if (matrix->sizeAt(0) != matrix->sizeAt(1))
THROW_EXCEPTION("ops::helpers::Hessenberg constructor: input array must be 2D square matrix !");
_H = matrix->dup(matrix->ordering());
_Q = matrix->ulike();
evalData();
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Hessenberg<T>::evalData() {
const int rows = _H->sizeAt(0);
std::vector<LongType> coeffsShape = {rows - 1};
NDArray hhCoeffs(_H->ordering(), coeffsShape, _H->dataType(), _H->getContext());
// calculate _H
for (LongType i = 0; i < rows - 1; ++i) {
T coeff, norm;
NDArray hRef = *_H;
NDArray *tail1Ptr = hRef({i + 1, -1, i, i + 1});
NDArray tail1 = *tail1Ptr;
delete tail1Ptr;
NDArray *tail2Ptr = hRef({i + 2, -1, i, i + 1}, true);
NDArray tail2 = *tail2Ptr;
delete tail2Ptr;
Householder<T>::evalHHmatrixDataI(tail1, coeff, norm);
NDArray *hViewPtr = hRef({0, 0, i, i + 1});
hViewPtr->template r<T>(i + 1) = norm;
delete hViewPtr;
hhCoeffs.template r<T>(i) = coeff;
NDArray *bottomRightCornerPtr = hRef({i + 1, -1, i + 1, -1}, true);
NDArray bottomRightCorner = *bottomRightCornerPtr;
delete bottomRightCornerPtr;
Householder<T>::mulLeft(bottomRightCorner, tail2, coeff);
NDArray *tail2Trans = tail2.transpose();
NDArray *rightColsPtr = hRef({0, 0, i + 1, -1}, true);
NDArray rightCols = *rightColsPtr;
delete rightColsPtr;
Householder<T>::mulRight(rightCols, *tail2Trans, coeff);
delete tail2Trans;
}
// calculate _Q
HHsequence hhSeq(_H, &hhCoeffs, 'u');
hhSeq._diagSize = rows - 1;
hhSeq._shift = 1;
hhSeq.applyTo_<T>(_Q);
// fill down with zeros starting at first subdiagonal
_H->fillAsTriangular<T>(0, -1, -1, *_H, 'l',false);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
Schur<T>::Schur(NDArray& matrix) {
if (matrix.rankOf() != 2) THROW_EXCEPTION("ops::helpers::Schur constructor: input matrix must be 2D !");
if (matrix.sizeAt(0) != matrix.sizeAt(1))
THROW_EXCEPTION("ops::helpers::Schur constructor: input array must be 2D square matrix !");
evalData(matrix);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::evalData(NDArray& matrix) {
auto res = matrix.reduceNumber(reduce::AMax);
const T scale = res->template t<T>(0);
delete res;
if (scale < DataTypeUtils::min_positive<T>()) {
t = matrix.ulike();
u = matrix.ulike();
t->nullify();
u->setIdentity();
return;
}
// perform Hessenberg decomposition
NDArray *matrixScale = matrix / scale;
Hessenberg<T> hess(matrixScale);
t = hess._H;
u = hess._Q;
calcFromHessenberg();
*t *= scale;
delete matrixScale;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::splitTwoRows(const int ind, const T shift) {
const int numCols = t->sizeAt(1);
T p = (T)0.5 * (t->t<T>(ind - 1, ind - 1) - t->t<T>(ind, ind));
T q = p * p + t->t<T>(ind, ind - 1) * t->t<T>(ind - 1, ind);
t->r<T>(ind, ind) += shift;
t->r<T>(ind - 1, ind - 1) += shift;
if (q >= (T)0) {
T z = math::sd_sqrt<T, T>(math::sd_abs<T,T>(q));
std::vector<LongType> rotShape = {2, 2};
NDArray rotation(t->ordering(), rotShape, t->dataType(), t->getContext());
if (p >= (T)0)
JacobiSVD<T>::createJacobiRotationGivens(p + z, t->t<T>(ind, ind - 1), rotation);
else
JacobiSVD<T>::createJacobiRotationGivens(p - z, t->t<T>(ind, ind - 1), rotation);
NDArray tRef = *t;
NDArray *rightColsPtr = tRef({0, 0, ind - 1, -1});
NDArray rightCols = *rightColsPtr;
delete rightColsPtr;
NDArray *rotT = rotation.transpose();
JacobiSVD<T>::mulRotationOnLeft(ind - 1, ind, rightCols, *rotT);
NDArray *topRowsPtr = tRef({0, ind + 1, 0, 0});
NDArray topRows = *topRowsPtr;
delete topRowsPtr;
JacobiSVD<T>::mulRotationOnRight(ind - 1, ind, topRows, rotation);
JacobiSVD<T>::mulRotationOnRight(ind - 1, ind, *u, rotation);
t->r<T>(ind, ind - 1) = (T)0;
delete rotT;
}
if (ind > 1) t->r<T>(ind - 1, ind - 2) = (T)0;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::calcShift(const int ind, const int iter, T& shift, NDArray& shiftVec) {
// shiftVec has length = 3
shiftVec.r<T>(0) = t->t<T>(ind, ind);
shiftVec.r<T>(1) = t->t<T>(ind - 1, ind - 1);
shiftVec.r<T>(2) = t->t<T>(ind, ind - 1) * t->t<T>(ind - 1, ind);
if (iter == 10) {
shift += shiftVec.t<T>(0);
for (int i = 0; i <= ind; ++i) t->r<T>(i, i) -= shiftVec.t<T>(0);
T s = math::sd_abs<T,T>(t->t<T>(ind, ind - 1)) + math::sd_abs<T,T>(t->t<T>(ind - 1, ind - 2));
shiftVec.r<T>(0) = T(0.75) * s;
shiftVec.r<T>(1) = T(0.75) * s;
shiftVec.r<T>(2) = T(-0.4375) * s * s;
}
if (iter == 30) {
T s = (shiftVec.t<T>(1) - shiftVec.t<T>(0)) / T(2.0);
s = s * s + shiftVec.t<T>(2);
if (s > T(0)) {
s = math::sd_sqrt<T, T>(s);
if (shiftVec.t<T>(1) < shiftVec.t<T>(0)) s = -s;
s = s + (shiftVec.t<T>(1) - shiftVec.t<T>(0)) / T(2.0);
s = shiftVec.t<T>(0) - shiftVec.t<T>(2) / s;
shift += s;
for (int i = 0; i <= ind; ++i) t->r<T>(i, i) -= s;
shiftVec = T(0.964);
}
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::initFrancisQR(const int ind1, const int ind2, NDArray& shiftVec, int& ind3,
NDArray& householderVec) {
// shiftVec has length = 3
for (ind3 = ind2 - 2; ind3 >= ind1; --ind3) {
const T mm = t->t<T>(ind3, ind3);
const T r = shiftVec.t<T>(0) - mm;
const T s = shiftVec.t<T>(1) - mm;
householderVec.r<T>(0) = (r * s - shiftVec.t<T>(2)) / t->t<T>(ind3 + 1, ind3) + t->t<T>(ind3, ind3 + 1);
householderVec.r<T>(1) = t->t<T>(ind3 + 1, ind3 + 1) - mm - r - s;
householderVec.r<T>(2) = t->t<T>(ind3 + 2, ind3 + 1);
if (ind3 == ind1) break;
const T lhs =
t->t<T>(ind3, ind3 - 1) * (math::sd_abs<T,T>(householderVec.t<T>(1)) + math::sd_abs<T,T>(householderVec.t<T>(2)));
const T rhs = householderVec.t<T>(0) * (math::sd_abs<T,T>(t->t<T>(ind3 - 1, ind3 - 1)) + math::sd_abs<T,T>(mm) +
math::sd_abs<T,T>(t->t<T>(ind3 + 1, ind3 + 1)));
if (math::sd_abs<T,T>(lhs) < DataTypeUtils::eps<T>() * rhs) break;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::doFrancisQR(const int ind1, const int ind2, const int ind3, NDArray& householderVec) {
if (!(ind2 >= ind1))
THROW_EXCEPTION(
"ops::helpers::Schur:doFrancisQR: wrong input indexes, condition ind2 >= ind1 must be true !");
if (!(ind2 <= ind3 - 2))
THROW_EXCEPTION(
"ops::helpers::Schur:doFrancisQR: wrong input indexes, condition iind2 <= ind3-2 must be true !");
const int numCols = t->sizeAt(1);
NDArray tRef = *t;
NDArray uRef = *u;
for (int k = ind2; k <= ind3 - 2; ++k) {
const bool firstIter = (k == ind2);
T coeff, normX;
std::vector<LongType> tailShape = {2,1};
NDArray tail(t->ordering(),tailShape, t->dataType(), t->getContext());
NDArray *firstPtr = firstIter ? &householderVec : tRef({k, k + 3, k - 1, k});
NDArray first = *firstPtr;
if (!firstIter) delete firstPtr;
Householder<T>::evalHHmatrixData(first, tail, coeff, normX);
if (normX != T(0)) {
if (firstIter && k > ind1)
t->r<T>(k, k - 1) = -t->t<T>(k, k - 1);
else if (!firstIter)
t->r<T>(k, k - 1) = normX;
NDArray *block1Ptr = tRef({k, k + 3, k, numCols}, true);
NDArray block1 = *block1Ptr;
delete block1Ptr;
Householder<T>::mulLeft(block1, tail, coeff);
NDArray *block2Ptr = tRef({0, math::sd_min<int>(ind3, k + 3) + 1, k, k + 3}, true);
NDArray block2 = *block2Ptr;
delete block2Ptr;
Householder<T>::mulRight(block2, tail, coeff);
NDArray *block3Ptr = uRef({0, numCols, k, k + 3}, true);
NDArray block3 = *block3Ptr;
delete block3Ptr;
Householder<T>::mulRight(block3, tail, coeff);
}
}
T coeff, normX;
std::vector<LongType> tailShape = {1,1};
NDArray tail(t->ordering(), tailShape, t->dataType(), t->getContext());
NDArray *firstPtr = tRef({ind3 - 1, ind3 + 1, ind3 - 2, ind3 - 1});
NDArray first = *firstPtr;
delete firstPtr;
Householder<T>::evalHHmatrixData(first, tail, coeff, normX);
if (normX != T(0)) {
t->r<T>(ind3 - 1, ind3 - 2) = normX;
NDArray *block1Ptr = tRef({ind3 - 1, ind3 + 1, ind3 - 1, numCols}, true);
NDArray block1 = *block1Ptr;
delete block1Ptr;
Householder<T>::mulLeft(block1, tail, coeff);
NDArray *block2Ptr = tRef({0, ind3 + 1, ind3 - 1, ind3 + 1}, true);
NDArray block2 = *block2Ptr;
delete block2Ptr;
Householder<T>::mulRight(block2, tail, coeff);
NDArray *block3Ptr = uRef({0, numCols, ind3 - 1, ind3 + 1}, true);
NDArray block3 = *block3Ptr;
delete block3Ptr;
Householder<T>::mulRight(block3, tail, coeff);
}
for (int i = ind2 + 2; i <= ind3; ++i) {
t->r<T>(i, i - 2) = T(0);
if (i > ind2 + 2) t->r<T>(i, i - 3) = T(0);
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Schur<T>::calcFromHessenberg() {
const int maxIters = _maxItersPerRow * t->sizeAt(0);
const int numCols = t->sizeAt(1);
int iu = numCols - 1;
int iter = 0;
int totalIter = 0;
T shift = T(0);
NDArray tRef = *t;
NDArray uRef = *u;
T norm = static_cast<T>(0);
for (int j = 0; j < numCols; ++j) {
NDArray *viewPtr = tRef({0, math::sd_min<int>(numCols, j + 2), j, j + 1});
auto sum = viewPtr->reduceNumber(reduce::ASum);
norm += sum->template t<T>(0);
delete viewPtr;
delete sum;
}
if (norm != T(0)) {
while (iu >= 0) {
const int il = getSmallSubdiagEntry(iu);
if (il == iu) {
t->r<T>(iu, iu) = t->t<T>(iu, iu) + shift;
if (iu > 0) t->r<T>(iu, iu - 1) = T(0);
iu--;
iter = 0;
} else if (il == iu - 1) {
splitTwoRows(iu, shift);
iu -= 2;
iter = 0;
} else {
std::vector<LongType> shiftVecShape = {3};
NDArray householderVec(t->ordering(), shiftVecShape, t->dataType(), t->getContext());
NDArray shiftVec(t->ordering(), shiftVecShape, t->dataType(), t->getContext());
calcShift(iu, iter, shift, shiftVec);
++iter;
++totalIter;
if (totalIter > maxIters) break;
int im;
initFrancisQR(il, iu, shiftVec, im, householderVec);
doFrancisQR(il, im, iu, householderVec);
}
}
}
}
BUILD_SINGLE_TEMPLATE( class Hessenberg, , SD_FLOAT_TYPES);
BUILD_SINGLE_TEMPLATE( class Schur, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
+616
View File
@@ -0,0 +1,616 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com), created on 05.06.2018
//
#ifndef LIBND4J_MMULHELPER_CPP
#define LIBND4J_MMULHELPER_CPP
#include "../MmulHelper.h"
#include <array/NDArrayFactory.h>
#include <helpers/BlasHelper.h>
#include <helpers/ShapeUtils.h>
#include <ops/declarable/headers/shape.h>
#include <ops/declarable/helpers/batched_gemm.h>
#include <algorithm>
#include <iterator>
#include <numeric>
#include <vector>
#include "ops/declarable/headers/blas.h"
namespace sd {
//////////////////////////////////////////////////////////////////////////
NDArray* MmulHelper::tensorDot(NDArray* A, NDArray* B,
const std::initializer_list<LongType>& axesA,
const std::initializer_list<LongType>& axesB) {
std::vector<LongType> aA(axesA);
std::vector<LongType> aB(axesB);
return tensorDot(A, B, aA, aB);
}
//////////////////////////////////////////////////////////////////////////
NDArray* MmulHelper::tensorDot(NDArray* A, NDArray* B, const std::vector<LongType>& axesA,
const std::vector<LongType>& axesB) {
std::vector<LongType> permutAt, permutBt;
std::vector<LongType> shapeAt, shapeBt;
auto outShape = ShapeUtils::evalShapeForTensorDot(A, B, axesA, axesB, permutAt, permutBt, shapeAt, shapeBt);
// check whether permutation is necessary
NDArray* aP = permutAt.empty() ? A : A->permute(permutAt, false, false);
NDArray* bP = permutBt.empty() ? B : B->permute(permutBt, false, false);
// check whether reshape is necessary
NDArray* aPR = aP->isSameShape(shapeAt) ? aP : aP->reshape(aP->ordering(), shapeAt);
NDArray* bPR = bP->isSameShape(shapeAt) ? bP : bP->reshape(bP->ordering(), shapeBt);
NDArray* c = mmul(aPR, bPR, nullptr, 1.0, 0.0);
c->reshapei(outShape);
// Delete reshaped arrays first
if(aPR != A && aPR != aP) {
delete aPR;
}
if(bPR != B && bPR != bP) {
delete bPR;
}
// Then delete permuted arrays
if(aP != A) {
delete aP;
}
if(bP != B) {
delete bP;
}
return c;
}
void MmulHelper::computeNewShapesAndAxes(
NDArray& as_, const std::vector<LongType>& axes_a,
NDArray& bs, const std::vector<LongType>& axes_b,
std::vector<LongType>& newshape_a, std::vector<LongType>& newaxes_a,
std::vector<LongType>& newshape_b, std::vector<LongType>& newaxes_b
) {
std::vector<LongType> *as_shape = as_.getShapeAsVector();
std::vector<LongType> *bs_shape = bs.getShapeAsVector();
std::vector<LongType> notin_a;
for(size_t k = 0; k < as_shape->size(); ++k) {
if(std::find(axes_a.begin(), axes_a.end(), k) == axes_a.end())
notin_a.push_back(k);
}
newaxes_a.clear();
std::copy(notin_a.begin(), notin_a.end(), std::back_inserter(newaxes_a));
std::copy(axes_a.begin(), axes_a.end(), std::back_inserter(newaxes_a));
LongType N2_a = std::accumulate(axes_a.begin(), axes_a.end(), 1L, [&](LongType product, LongType i){
return product * (*as_shape)[i];
});
newshape_a.clear();
newshape_a.push_back(std::accumulate(notin_a.begin(), notin_a.end(), 1L, [&](LongType product, LongType i){
return product * (*as_shape)[i];
}));
newshape_a.push_back(N2_a);
std::vector<LongType> notin_b;
for(size_t k = 0; k < bs_shape->size(); ++k) {
if(std::find(axes_b.begin(), axes_b.end(), k) == axes_b.end())
notin_b.push_back(k);
}
newaxes_b.clear();
std::copy(axes_b.begin(), axes_b.end(), std::back_inserter(newaxes_b));
std::copy(notin_b.begin(), notin_b.end(), std::back_inserter(newaxes_b));
LongType N2_b = std::accumulate(axes_b.begin(), axes_b.end(), 1L, [&](LongType product, LongType i){
return product * (*bs_shape)[i];
});
newshape_b.clear();
newshape_b.push_back(N2_b);
newshape_b.push_back(std::accumulate(notin_b.begin(), notin_b.end(), 1L, [&](LongType product, LongType i){
return product * (*bs_shape)[i];
}));
}
//////////////////////////////////////////////////////////////////////////
void MmulHelper::tensorDot2(NDArray* a, NDArray* b, NDArray* c, const std::vector<LongType>& axes_a,
const std::vector<LongType>& axes_b, std::vector<LongType>& permutAt,
std::vector<LongType>& permuteBt, std::vector<LongType>& permuteCt,
NDArray* realFinalResult) {
// check whether permutation is required
NDArray* cP =permuteCt.empty() ? c : c->permute(permuteCt, false, false);
std::vector<LongType> shapeAt, shapeBt;
std::vector<LongType> permutAtDummy, permuteBtDummy;
std::vector<LongType> newshape_a, newaxes_a, newshape_b, newaxes_b;
computeNewShapesAndAxes(*a, axes_a, *b, axes_b, newshape_a, newaxes_a, newshape_b, newaxes_b);
NDArray* aP = permutAt.empty() ? a : a->permute(permutAt, false, false);
NDArray* bP = permuteBt.empty() ? b :b->permute(permuteBt, false, false);
NDArray* aPermuted = aP->permute(newaxes_a, false, false);
NDArray* aPR = aPermuted->reshape('c', newshape_a, true);
NDArray* bPermuted = bP->permute(newaxes_b, false, false);
NDArray* bPR = bPermuted->reshape('c', newshape_b, true);
std::vector<LongType> requiredCshape = {aPR->sizeAt(0), bPR->sizeAt(1)};
NDArray *cP2 = cP->reshape('f', requiredCshape, false);
NDArray* cPR = cP2;
NDArray * ret = mmul(aPR, bPR, cPR, 1.0, 0.0);
if (cPR->buffer() != cP->buffer() ||
cPR->specialBuffer() != cP->specialBuffer()) { // this means both permute and reshape have been performed on c, cP
if(c->buffer() == cP->buffer()) {
auto copyFromBuff = cP->dataBuffer();
cP->dataBuffer()->copyBufferFrom(*copyFromBuff);
} else {
auto copyFromBuff = cP->dataBuffer();
c->dataBuffer()->copyBufferFrom(*copyFromBuff);
}
}
if(realFinalResult != c) {
realFinalResult->dataBuffer()->copyBufferFrom(*c->dataBuffer());
}
if(cP != c) {
delete cP;
}
if(cPR != c) {
delete cPR;
}
if(aP != a && !aP->isView()) {
delete aP;
}
if(bP != b && !bP->isView()) {
delete bP;
}
// Delete in reverse order of creation to avoid use-after-free
if(bPR != b && bPR != bP && bPR != bPermuted && !bPR->isView()) {
delete bPR;
}
if(bPermuted != b && bPermuted != bP && !bPermuted->isView()) {
delete bPermuted;
}
if(aPR != a && aPR != aP && aPR != aPermuted && !aPR->isView()) {
delete aPR;
}
if(aPermuted != a && aPermuted != aP && !aPermuted->isView()) {
delete aPermuted;
}
}
void MmulHelper::tensorDot(NDArray* a, NDArray* b, NDArray* c,
std::vector<LongType>& axes_a, std::vector<LongType>& axes_b,
std::vector<LongType>& permutForC) {
std::vector<LongType> permutAt, permutBt;
std::vector<LongType> shapeAt, shapeBt;
ShapeUtils::evalShapeForTensorDot(a, b, axes_a, axes_b, permutAt, permutBt, shapeAt, shapeBt);
// check whether permutation is required
NDArray* cP = permutForC.empty() ? c :c->permute(permutForC, false, false);
// check whether permutation is necessary
NDArray* aP = permutAt.empty() ? a :a->permute(permutAt, false, false);
NDArray* bP = permutBt.empty() ? b : b->permute(permutBt, false, false);
// check whether reshape is necessary
NDArray* aPR = aP->isSameShape(shapeAt) ? aP : aP->reshape(aP->ordering(), shapeAt);
NDArray* bPR = bP->isSameShape(shapeAt) ? bP : bP->reshape(bP->ordering(), shapeBt);
std::vector<LongType> requiredCshape = {aPR->sizeAt(0), bPR->sizeAt(1)};
NDArray* cPR = cP->isSameShape(requiredCshape) ? cP : cP->reshape(cP->ordering(), requiredCshape, false);
NDArray *ret = mmul(aPR, bPR, cPR, 1.0, 0.0);
if (c != ret) { // this means both permute and reshape have been performed on c, cP
// always points on c->buffer()
NDArray *assign2 = ret->reshape(c->ordering(),requiredCshape);
c->assign(assign2);
delete assign2;
}
if(c != cP && !cP->isView()) {
delete cP;
}
if(aP != a && !aP->isView()) {
delete aP;
}
if(bP != b && !bP->isView()) {
delete bP;
}
if(aPR != a && aPR != aP && !aPR->isView()) {
delete aPR;
}
if(bPR != b && bPR != bP && !bPR->isView()) {
delete bPR;
}
if(cPR != c && cPR != cP && !cPR->isView()) {
delete cPR;
}
}
#ifndef __JAVACPP_HACK__
//////////////////////////////////////////////////////////////////////////
void MmulHelper::tensorDot(NDArray* a, NDArray* b, NDArray* c,
std::vector<std::vector<LongType>>& modifA,
std::vector<std::vector<LongType>>& modifB,
std::vector<std::vector<LongType>>& modifC) {
NDArray *aPR(const_cast<NDArray*>(a)), *bPR(const_cast<NDArray*>(b));
std::string whatToDoWithA, whatToDoWithB,
whatToDoWithC; // "" - nothing; "p" - permutation; "r" - reshaping; "pr" - permutation+reshaping; "rp" -
// reshaping/permutation, and so on; if another string is produced - throw exception
for (const auto& arr : modifA)
whatToDoWithA =
(std::find(arr.begin(), arr.end(), 0) != arr.end())
? whatToDoWithA + "p"
: whatToDoWithA +
"r"; // when 0 is present in arr then it is permutation array, otherwise - it is reshaping array
for (const auto& arr : modifB)
whatToDoWithB = (std::find(arr.begin(), arr.end(), 0) != arr.end()) ? whatToDoWithB + "p" : whatToDoWithB + "r";
for (const auto& arr : modifC)
whatToDoWithC = (std::find(arr.begin(), arr.end(), 0) != arr.end()) ? whatToDoWithC + "p" : whatToDoWithC + "r";
// first step for a array
if (!whatToDoWithA.empty())
aPR = (whatToDoWithA[0] == 'p') ? a->permute(modifA[0], false, false)
:a->reshape(a->ordering(), modifA[0]);
// first step for b array
if (!whatToDoWithB.empty())
bPR = (whatToDoWithB[0] == 'p') ? b->permute(modifB[0], false, false)
: b->reshape(b->ordering(), modifB[0]);
// rest steps for a array
for (size_t i = 1; i < whatToDoWithA.size(); ++i)
if (whatToDoWithA[i] == 'p')
aPR->permutei(modifA[i], false, false);
else
aPR->reshapei(modifA[i]);
// rest steps for b array
for (size_t i = 1; i < whatToDoWithB.size(); ++i)
if (whatToDoWithB[i] == 'p')
bPR->permutei(modifB[i], false, false);
else
bPR->reshapei(modifB[i]);
// now work with c array
std::vector<NDArray*> cArrs = {c};
if (!whatToDoWithC.empty()) {
cArrs = std::vector<NDArray*>(whatToDoWithC.size() + 1, c);
for (size_t i = 0; i < cArrs.size() - 1; ++i)
cArrs[i + 1] =
(whatToDoWithC[i] == 'p')
? cArrs[i]->permute(modifC[i], false, false)
: cArrs[i]->reshape(
c->ordering(), modifC[i],
false); // since we ignore first element in cArrs (that is cArrs[0]) then it is always equal to c
}
mmul(aPR, bPR, cArrs[cArrs.size() - 1], 1.0, 0.0);
// check whether new buffer allocation was happened for c array
if (!whatToDoWithC.empty()) {
for (int i = cArrs.size() - 1; i > 0; --i) {
if (cArrs[i]->buffer() != cArrs[i - 1]->buffer() || cArrs[i]->specialBuffer() != cArrs[i - 1]->specialBuffer())
cArrs[i - 1]->assign(cArrs[i]);
delete cArrs[i];
}
}
if (aPR != a) delete aPR;
if (bPR != b) delete bPR;
}
//////////////////////////////////////////////////////////////////////////
NDArray* MmulHelper::tensorDot(NDArray* a, NDArray* b,
std::vector<std::vector<LongType>>& modifA,
std::vector<std::vector<LongType>>& modifB) {
NDArray *aPR(const_cast<NDArray*>(a)), *bPR(const_cast<NDArray*>(b));
std::string whatToDoWithA,
whatToDoWithB; // "" - nothing; "p" - permutation only; "r" - reshaping only; "pr" - permutation+reshaping; "rp"
// - reshaping/permutation; another string - throw exception
for (const auto& arr : modifA)
whatToDoWithA =
(std::find(arr.begin(), arr.end(), 0) != arr.end())
? whatToDoWithA + "p"
: whatToDoWithA +
"r"; // when 0 is present in arr then it is permutation array, otherwise - it is reshaping array
for (const auto& arr : modifB)
whatToDoWithB = (std::find(arr.begin(), arr.end(), 0) != arr.end()) ? whatToDoWithB + "p" : whatToDoWithB + "r";
// first step for a array
if (!whatToDoWithA.empty())
aPR = (whatToDoWithA[0] == 'p') ?a->permute(modifA[0], false, false)
: a->reshape(a->ordering(), modifA[0]);
// first step for b array
if (!whatToDoWithB.empty())
bPR = (whatToDoWithB[0] == 'p') ? b->permute(modifB[0], false, false)
: b->reshape(b->ordering(), modifB[0]);
// rest steps for a array
for (size_t i = 1; i < whatToDoWithA.size(); ++i)
if (whatToDoWithA[i] == 'p')
aPR->permutei(modifA[i], false, false);
else
aPR->reshapei(modifA[i]);
// rest steps for b array
for (size_t i = 1; i < whatToDoWithB.size(); ++i)
if (whatToDoWithB[i] == 'p')
bPR->permutei(modifB[i], false, false);
else
bPR->reshapei(modifB[i]);
NDArray* result = mmul(aPR, bPR, nullptr, 1.0, 0.0);
return result;
}
#endif
//////////////////////////////////////////////////////////////////////////
NDArray* MmulHelper::mmul(NDArray* A, NDArray* B, NDArray* C, const double alpha,
const double beta, const char outOrder) {
LongType lenDim;
const LongType aRank = A->rankOf();
const LongType bRank = B->rankOf();
const bool isAVector = shape::isCommonVector(A->shapeInfo(), lenDim);
const bool isBVector = shape::isCommonVector(B->shapeInfo(), lenDim);
// dot product of 2 vectors
if (A->lengthOf() == B->lengthOf() && isAVector && isBVector &&
(aRank != 2 ||
(aRank == 2 && (A->isSameShape(B) ||
(bRank == 1 && A->sizeAt(1) == 1))))) { // (1x1x1 * 1x1) or (1x4 * 1*4) or (4x1 * 4x1) or (4x1 * 4)
return dot(A, B, C, alpha, beta);
}
// matrix x matrix
if (aRank == 2 && bRank == 2) {
return mmulMxM(A, B, C, alpha, beta, outOrder);
}
// matrix x vector
if (aRank == 2 && isBVector) {
return mmulMxV(A, B, C, alpha, beta, outOrder);
}
// vector x matrix, A{M} x B{M,N} = C{N} -> reduce to matrix x matrix A2{1,M} x B{M,N} = C2{1,N}, since there is no
// corresponding blas operation sgevm
if (isAVector && bRank == 2) {
std::vector<sd::LongType> aShape = {1, A->lengthOf()};
std::vector<sd::LongType> cShape = {1, C->lengthOf()};
NDArray* A2 = A->reshape(A->ordering(), aShape); // A{M} -> A2{1,M}
NDArray* C2 = C ? C->reshape(C->ordering(), cShape, false) : nullptr; // C{N} -> C2{1,N}
auto result = mmulMxM(A2, B, C2, alpha, beta, outOrder); // result{1,N}
// Cleanup reshaped arrays
if (A2 != A) delete A2;
if (C2 != nullptr && C2 != C) delete C2;
if (!C) {
result->reshapei({result->lengthOf()}); // result{1,N} -> result{N}
return result;
}
return C;
}
// batched matrix multiplication
return mmulNxN(A, B, C, alpha, beta, outOrder);
}
bool MmulHelper::resolveTranspose(sd::NDArray& a, sd::NDArray& b, bool& transA, bool& transB) {
int rowsA = a.sizeAt(-2);
int colsA = a.sizeAt(-1);
int rowsB = b.sizeAt(-2);
int colsB = b.sizeAt(-1);
transA = false;
transB = false;
if (colsA == rowsB) {
// No transpose needed
return true;
} else if (rowsA == rowsB) {
// Transpose A
transA = true;
return true;
} else if (colsA == colsB) {
// Transpose B
transB = true;
return true;
} else {
// Dimensions do not match for matrix multiply
return false;
}
}
//////////////////////////////////////////////////////////////////////////
void MmulHelper::matmul(NDArray* x, NDArray* y, NDArray* z, const bool transX, const bool transY, double alpha,
double beta, NDArray* realFinalResult) {
int xRank = x->rankOf();
int yRank = y->rankOf();
auto outShape = ShapeUtils::evalShapeForMatmul(x->shapeInfo(), y->shapeInfo(), transX, transY);
if (!z->isSameShape(outShape)) {
std::string errorMessage;
errorMessage = "NDArrayFactory::matmul static method: input shape of output array is wrong, actual is";
errorMessage += ShapeUtils::shapeAsString(z).c_str();
errorMessage += " and expected is ";
errorMessage += ShapeUtils::shapeAsString(outShape).c_str();
errorMessage += " ! \n";
THROW_EXCEPTION(errorMessage.c_str());
}
if (z->isEmpty()) return;
NDArray *xT = const_cast<NDArray *>(x);
NDArray *yT = const_cast<NDArray *>(y);
NDArray *zT = z;
// Handle transpose via permute + dup for contiguous data
// permute creates a view with swapped strides, dup() makes a contiguous copy
if ((transX && xRank > 1) || (transY && yRank > 1)) {
const int rank = xRank >= yRank ? xRank : yRank;
std::vector<LongType> permut(rank);
for (int i = 0; i < rank - 2; ++i) permut[i] = i;
permut[rank - 2] = rank - 1;
permut[rank - 1] = rank - 2;
if (transX) {
NDArray *permutedView = x->permute(permut, false, false); // Create view (non-contiguous)
xT = permutedView->dup(); // Make contiguous copy with proper data layout
delete permutedView;
}
if (transY) {
NDArray *permutedView = y->permute(permut, false, false); // Create view (non-contiguous)
yT = permutedView->dup(); // Make contiguous copy with proper data layout
delete permutedView;
}
}
if (xRank <= 2 && yRank <= 2) {
// dot (1Dx1D), vector-matrix (1Dx2D), matrix-vector (2Dx1D), matrix-matrix (2Dx2D) product cases
NDArray* xReshaped = nullptr;
NDArray* zReshaped = nullptr;
if (xRank == 1 && yRank == 2) {
// reduce vector-matrix to matrix-matrix case
std::vector<sd::LongType> xShape = {1, xT->lengthOf()};
std::vector<sd::LongType> zShape = {1, z->lengthOf()};
// Remember if we need to delete the permuted versions
NDArray* xPermuted = (xT != x) ? xT : nullptr;
NDArray* zPermuted = (zT != z) ? zT : nullptr;
xReshaped = xT->reshape(xT->ordering(), xShape, false);
xT = xReshaped;
zReshaped = z->reshape(z->ordering(), zShape, false);
zT = zReshaped;
// Clean up permuted versions if they exist
if(xPermuted != nullptr && !xPermuted->isView()) {
delete xPermuted;
}
if(zPermuted != nullptr && !zPermuted->isView()) {
delete zPermuted;
}
}
mmul(xT, yT, zT, alpha, beta);
// Copy back result and clean up reshaped output
if(zT != z) {
z->dataBuffer()->copyBufferFrom(*zT->dataBuffer(), zT->lengthOf() * zT->sizeOfT());
delete zT;
zT = z; // Reset to original to prevent double-free at end of function
}
// Clean up reshaped input
if(xReshaped != nullptr && xReshaped != x) {
delete xReshaped;
xT = x; // Reset to original to prevent double-free at end of function
}
} else {
// Batched matmul: loop over batch dimensions and call 2D gemm for each slice
// This is more reliable than mmulNxN which has bugs in batch index calculation
// For 3D arrays [batch, M, K] x [batch, K, N] = [batch, M, N]
// We iterate over batch dimension and call 2D mmul for each slice
const int xRankT = xT->rankOf();
const int yRankT = yT->rankOf();
const int zRankT = zT->rankOf();
if (xRankT == 3 && yRankT == 3 && zRankT == 3) {
// Simple case: all 3D with matching batch dimension
const LongType batchSize = xT->sizeAt(0);
const LongType M = xT->sizeAt(1);
const LongType K = xT->sizeAt(2);
const LongType N = yT->sizeAt(2);
for (LongType b = 0; b < batchSize; ++b) {
// Get 2D slices for this batch using subarray
auto xSlice = (*xT)(b, {0}); // [M, K]
auto ySlice = (*yT)(b, {0}); // [K, N]
auto zSlice = (*zT)(b, {0}); // [M, N]
// Call 2D matmul - no transpose flags since we already handled them via permute+dup
mmul(xSlice, ySlice, zSlice, alpha, beta);
}
} else {
// Fall back to mmulNxN for other cases (4D+, mixed ranks, etc.)
mmulNxN(xT, yT, zT, alpha, beta, z->ordering());
}
}
// Clean up permuted arrays (works for both cases)
if (xT != x && xT != nullptr) delete xT;
if (yT != y && yT != nullptr) delete yT;
if(realFinalResult != nullptr && realFinalResult != z) {
realFinalResult->dataBuffer()->copyBufferFrom(*z->dataBuffer());
}
}
} // namespace sd
#endif
@@ -0,0 +1,121 @@
#include <helpers/ModularHasher.h>
#include <cstring>
namespace sd {
namespace helpers {
namespace detail {
const uint64_t GOLDEN_RATIO = 0x9e3779b97f4a7c15ULL;
const uint64_t INITIAL_HASH = 14695981039346656037ULL;
// Specialization for uint64_t
template<> uint64_t SIMDHasher<uint64_t>::hash_chunk(const uint64_t* data, size_t size, uint64_t initial_hash) {
uint64_t hash = initial_hash;
#if defined(__ARM_NEON)
uint64x2_t hash_vec = vdupq_n_u64(initial_hash);
const uint64x2_t golden = vdupq_n_u64(GOLDEN_RATIO);
for (size_t i = 0; i < size - 1; i += 2) {
uint64x2_t val = vld1q_u64(data + i);
hash_vec = veorq_u64(hash_vec, val);
// Extract lower 32 bits of each 64-bit lane
uint32x2_t low_hash = vmovn_u64(hash_vec);
uint32x2_t low_golden = vmovn_u64(golden);
// Perform 32x32 -> 64 bit widening multiply
hash_vec = vmull_u32(low_hash, low_golden);
}
uint64_t tmp[2];
vst1q_u64(tmp, hash_vec);
hash = tmp[0] ^ tmp[1];
#elif defined(__AVX2__)
__m256i hash_vec = _mm256_set1_epi64x(initial_hash);
const __m256i golden_vec = _mm256_set1_epi64x(GOLDEN_RATIO);
for (size_t i = 0; i < size - 3; i += 4) {
__m256i val = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(data + i));
hash_vec = _mm256_xor_si256(hash_vec, val);
hash_vec = _mm256_mul_epi32(hash_vec, golden_vec);
}
uint64_t tmp[4];
_mm256_storeu_si256(reinterpret_cast<__m256i*>(tmp), hash_vec);
hash = tmp[0] ^ tmp[1] ^ tmp[2] ^ tmp[3];
#elif defined(__SSE4_2__)
__m128i hash_vec = _mm_set1_epi64x(initial_hash);
const __m128i golden_vec = _mm_set1_epi64x(GOLDEN_RATIO);
for (size_t i = 0; i < size - 1; i += 2) {
__m128i val = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + i));
hash_vec = _mm_xor_si128(hash_vec, val);
hash_vec = _mm_mul_epi32(hash_vec, golden_vec);
}
uint64_t tmp[2];
_mm_storeu_si128(reinterpret_cast<__m128i*>(tmp), hash_vec);
hash = tmp[0] ^ tmp[1];
#else
if(size >= 4) {
// Scalar fallback with unrolling
for (size_t i = 0; i < size - 3; i += 4) {
hash ^= data[i];
hash = (hash * GOLDEN_RATIO) ^ (hash >> 32);
hash ^= data[i+1];
hash = (hash * GOLDEN_RATIO) ^ (hash >> 32);
hash ^= data[i+2];
hash = (hash * GOLDEN_RATIO) ^ (hash >> 32);
hash ^= data[i+3];
hash = (hash * GOLDEN_RATIO) ^ (hash >> 32);
}
}
#endif
// Handle remaining elements
size_t remainder = size % 4;
if(size >= 4) {
size_t start = size - remainder;
for (size_t i = start; i < size; i++) {
hash ^= data[i];
hash = (hash * GOLDEN_RATIO) ^ (hash >> 32);
}
}
return hash;
}
// Specialization for double
uint64_t DataChunkHasher<double>::hash_data(const double* data, size_t size, uint64_t initial_hash) {
return SIMDHasher<uint64_t>::hash_chunk(
reinterpret_cast<const uint64_t*>(data),
size,
initial_hash
);
}
uint64_t ModularHasher::combine_hashes(std::initializer_list<uint64_t> hashes) {
uint64_t result = INITIAL_HASH;
for (uint64_t h : hashes) {
result ^= h;
result = (result * GOLDEN_RATIO) ^ (result >> 32);
}
return result;
}
uint64_t ModularHasher::hash_scalar(uint64_t value, uint64_t initial_hash) {
uint64_t hash = initial_hash;
hash ^= value;
return (hash * GOLDEN_RATIO) ^ (hash >> 32);
}
// Explicit template instantiations
template uint64_t ModularHasher::hash_vector<uint64_t>(const std::vector<uint64_t>&, uint64_t);
template uint64_t ModularHasher::hash_vector<double>(const std::vector<double>&, uint64_t);
template uint64_t ModularHasher::hash_vector<int64_t>(const std::vector<int64_t>&, uint64_t);
} // namespace detail
} // namespace helpers
} // namespace sd
@@ -0,0 +1,103 @@
/* ******************************************************************************
*
*
* 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 6/30/2018
// @author Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/OmpLaunchHelper.h>
#include <math/templatemath.h>
#include <system/Environment.h>
namespace sd {
////////////////////////////////////////////////////////////////////////////////
OmpLaunchHelper::OmpLaunchHelper(const LongType N, float desiredNumThreads) {
auto maxItersPerThread = Environment::getInstance().elementwiseThreshold();
if (N < maxItersPerThread)
_numThreads = 1;
else {
#ifdef _OPENMP
if (desiredNumThreads == -1)
desiredNumThreads = omp_get_max_threads();
else if (desiredNumThreads < 1)
desiredNumThreads = 1;
else
desiredNumThreads = sd::math::sd_min<int>(omp_get_max_threads(), desiredNumThreads);
#else
desiredNumThreads = Environment::getInstance().maxThreads();
#endif
_numThreads = sd::math::sd_min<int>(N / maxItersPerThread, desiredNumThreads);
}
_itersPerThread = N / _numThreads;
_remainder = N % _numThreads; // last thread may contain bigger number of iterations
}
LongType OmpLaunchHelper::betterSpan(LongType N) { return betterSpan(N, betterThreads(N)); }
LongType OmpLaunchHelper::betterSpan(LongType N, LongType numThreads) {
auto r = N % numThreads;
auto t = N / numThreads;
if (r == 0)
return t;
else {
// breaks alignment
return t + 1;
}
}
int OmpLaunchHelper::betterThreads(LongType N) {
#ifdef _OPENMP
return betterThreads(N, omp_get_max_threads());
#else
return betterThreads(N, Environment::getInstance().maxThreads());
;
#endif
}
int OmpLaunchHelper::betterThreads(LongType N, int maxThreads) {
auto t = Environment::getInstance().elementwiseThreshold();
if (N < t)
return 1;
else {
return static_cast<int>(sd::math::sd_min<LongType>(N / t, maxThreads));
}
}
int OmpLaunchHelper::tadThreads(LongType tadLength, LongType numTads) {
#ifdef _OPENMP
auto maxThreads = omp_get_max_threads();
#else
auto maxThreads = Environment::getInstance().maxThreads();
#endif
// if there's only 1 thread allowed - nothing to do here
if (maxThreads <= 1) return 1;
auto totalLength = tadLength * numTads;
// if array is tiny - no need to spawn any threeds
if (totalLength < Environment::getInstance().elementwiseThreshold()) return 1;
// by default we're spawning as many threads we can, but not more than number of TADs
return sd::math::sd_min<int>(numTads, maxThreads);
}
} // namespace sd
@@ -0,0 +1,141 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com), created on 15.07.2018
//
#include <helpers/OpArgsHolder.h>
namespace sd {
////////////////////////////////////////////////////////////////////////
// default constructor
OpArgsHolder::OpArgsHolder() {
_inArrs = std::vector<NDArray*>();
_tArgs = std::vector<double>();
_iArgs = std::vector<sd::LongType>();
_bArgs = std::vector<bool>();
_isArrAlloc = std::vector<bool>();
_numInArrs = 0;
_numTArgs = 0;
_numIArgs = 0;
_numBArgs = 0;
}
////////////////////////////////////////////////////////////////////////
// copy constructor
OpArgsHolder::OpArgsHolder(const OpArgsHolder& other) {
THROW_EXCEPTION("OpArgsHolder::OpArgsHolder copy constructor: don't use me !");
}
////////////////////////////////////////////////////////////////////////
// constructor
OpArgsHolder::OpArgsHolder(const std::vector<NDArray*>& inArrs, const std::vector<double>& tArgs,
const std::vector<sd::LongType>& iArgs, const std::vector<bool>& bArgs) {
_inArrs = inArrs;
_tArgs = tArgs;
_iArgs = iArgs;
_bArgs = bArgs;
_isArrAlloc = std::vector<bool>();
_numInArrs = _inArrs.size();
_numTArgs = _tArgs.size();
_numIArgs = _iArgs.size();
_numBArgs = _bArgs.size();
}
////////////////////////////////////////////////////////////////////////
// move constructor
OpArgsHolder::OpArgsHolder(OpArgsHolder&& other) noexcept
: _inArrs(std::move(other._inArrs)),
_tArgs(std::move(other._tArgs)),
_iArgs(std::move(other._iArgs)),
_bArgs(std::move(other._bArgs)),
_isArrAlloc(std::move(other._isArrAlloc)) {
other._isArrAlloc = std::vector<bool>();
_numInArrs = _inArrs.size();
_numTArgs = _tArgs.size();
_numIArgs = _iArgs.size();
_numBArgs = _bArgs.size();
}
////////////////////////////////////////////////////////////////////////
// assignment operator
OpArgsHolder& OpArgsHolder::operator=(const OpArgsHolder& other) {
return *this;
}
////////////////////////////////////////////////////////////////////////
// move assignment operator
OpArgsHolder& OpArgsHolder::operator=(OpArgsHolder&& other) noexcept {
if (this == &other) return *this;
for (size_t i = 0; i < _isArrAlloc.size(); ++i) // delete arrays if necessary
if (_isArrAlloc[i]) delete _inArrs[i];
_inArrs = std::move(other._inArrs);
_tArgs = std::move(other._tArgs);
_iArgs = std::move(other._iArgs);
_bArgs = std::move(other._bArgs);
_isArrAlloc = std::move(other._isArrAlloc);
other._isArrAlloc = std::vector<bool>();
_numInArrs = _inArrs.size();
_numTArgs = _tArgs.size();
_numIArgs = _iArgs.size();
_numBArgs = _bArgs.size();
return *this;
}
////////////////////////////////////////////////////////////////////////
OpArgsHolder OpArgsHolder::createArgsHolderForBP(const std::vector<NDArray*>& inGradArrs, const bool isInPlace) const {
const int numInGradArrs = inGradArrs.size();
OpArgsHolder result(std::vector<NDArray*>(_numInArrs + numInGradArrs, nullptr), _tArgs, _iArgs);
if (isInPlace) result._isArrAlloc = std::vector<bool>(_numInArrs + numInGradArrs, false);
for (int i = 0; i < _numInArrs; ++i) {
if (isInPlace) {
NDArray &arr2 = *_inArrs[i];
result._inArrs[i] = new NDArray(arr2); // make copy
result._isArrAlloc[i] = true;
} else
result._inArrs[i] = _inArrs[i];
}
// input gradients
for (int i = 0; i < numInGradArrs; ++i) result._inArrs[_numInArrs + i] = inGradArrs[i];
return result;
}
////////////////////////////////////////////////////////////////////////
// default destructor
OpArgsHolder::~OpArgsHolder() noexcept {
for (size_t i = 0; i < _isArrAlloc.size(); ++i)
if (_isArrAlloc[i]) delete _inArrs[i];
}
} // namespace sd
+115
View File
@@ -0,0 +1,115 @@
/* ******************************************************************************
*
*
* 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
//
#include <helpers/OpTracker.h>
#include <helpers/logger.h>
#include <legacy/NativeOps.h>
#include <sstream>
using namespace sd::ops;
using namespace sd::graph;
namespace sd {
OpTracker& OpTracker::getInstance() {
static OpTracker instance;
return instance;
}
void OpTracker::storeOperation(::graph::OpType opType, const OpDescriptor& descriptor) {
// check out CPU features
if (!isMinimalRequirementsMet()) {
auto binaryLevel = ::binaryLevel();
auto optimalLevel = ::optimalLevel();
switch (binaryLevel) {
case 3: {
sd_printf(
"libnd4j binary was built with AVX512 support, but current CPU doesn't have this instruction set. Exiting "
"now...",
"");
} break;
case 2: {
sd_printf(
"libnd4j binary was built with AVX/AVX2 support, but current CPU doesn't have this instruction set. "
"Exiting now...",
"");
} break;
default: {
sd_printf("Unknown binary validation error. Exiting now...", "");
} break;
}
// we're exiting now
exit(119);
}
//
if (_map.count(opType) < 1) {
std::vector<OpDescriptor> vec;
_map[opType] = vec;
}
_operations++;
auto vec = _map[opType];
if (std::find(vec.begin(), vec.end(), descriptor) == vec.end()) _map[opType].emplace_back(descriptor);
}
void OpTracker::storeOperation(::graph::OpType opType, const char* opName, const LongType opNum) {
OpDescriptor descriptor(0, opName, false);
descriptor.setOpNum((int)opNum);
descriptor.setHash(-1);
storeOperation(opType, descriptor);
}
template <typename T>
std::string OpTracker::local_to_string(T value) {
std::ostringstream os;
os << value;
return os.str();
}
int OpTracker::totalGroups() { return (int)_map.size(); }
int OpTracker::totalOperations() { return _operations; }
const char* OpTracker::exportOperations() {
if (_export.length() == 0) {
for (auto& v : _map) {
std::string block = local_to_string(v.first) + " ";
for (auto& i : v.second) {
block += local_to_string(i.getHash()) + ":";
block += local_to_string(i.getOpNum()) + ":";
block += *i.getOpName() + "<<";
}
block += ">>";
_export += block;
}
}
return _export.c_str();
}
} // namespace sd
@@ -0,0 +1,191 @@
/* ******************************************************************************
*
*
* 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
//
#include <graph/RandomGenerator.h>
#include <helpers/RandomLauncher.h>
#include <types/float16.h>
#include <helpers/PointersManager.h>
#include <legacy/NativeOpExecutioner.h>
namespace sd {
void RandomLauncher::applyDropOut(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double retainProb, NDArray* z) {
if (z == nullptr) z = array;
ExtraArguments arguments({retainProb});
PointersManager pm(context, "applyDropOut");
NDArray::prepareSpecialUse({z}, {array});
NativeOpExecutioner::execRandom(context, random::DropOut, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), z->buffer(), z->shapeInfo(),
z->specialBuffer(), z->specialShapeInfo(), arguments.argumentsAsT(z->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({z}, {array});
}
void RandomLauncher::applyInvertedDropOut(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double retainProb, NDArray* z) {
if (z == nullptr) z = array;
ExtraArguments arguments({retainProb});
PointersManager pm(context, "applyInvertedDropOut");
NDArray::prepareSpecialUse({z}, {array});
NativeOpExecutioner::execRandom(context, random::DropOutInverted, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), z->buffer(), z->shapeInfo(),
z->specialBuffer(), z->specialShapeInfo(), arguments.argumentsAsT(z->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({z}, {array});
}
void RandomLauncher::applyAlphaDropOut(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double retainProb, double alpha, double beta, double alphaPrime, NDArray* z) {
if (z == nullptr) z = array;
ExtraArguments arguments({retainProb, alpha, beta, alphaPrime});
PointersManager pm(context, "applyAlphaDropOut");
NDArray::prepareSpecialUse({z}, {array});
NativeOpExecutioner::execRandom(context, random::AlphaDropOut, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), z->buffer(), z->shapeInfo(),
z->specialBuffer(), z->specialShapeInfo(), arguments.argumentsAsT(z->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({z}, {array});
}
void RandomLauncher::fillBernoulli(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double prob) {
ExtraArguments arguments({prob});
PointersManager pm(context, "fillBernoulli");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::BernoulliDistribution, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(),
arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillUniform(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double from, double to) {
ExtraArguments arguments({from, to});
PointersManager pm(context, "fillUniform");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::UniformDistribution, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(),
arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillGaussian(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double mean, double stdev) {
ExtraArguments arguments({mean, stdev});
PointersManager pm(context, "fillGaussian");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::GaussianDistribution, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), array->buffer(),
array->shapeInfo(), array->specialBuffer(), array->specialShapeInfo(),
array->buffer(), array->shapeInfo(), array->specialBuffer(),
array->specialShapeInfo(), arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillExponential(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double lambda) {
ExtraArguments arguments({lambda});
PointersManager pm(context, "fillExponential");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::ExponentialDistribution, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(),
arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillLogNormal(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double mean, double stdev) {
ExtraArguments arguments({mean, stdev});
PointersManager pm(context, "fillLogNormal");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::GaussianDistribution, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), array->buffer(),
array->shapeInfo(), array->specialBuffer(), array->specialShapeInfo(),
array->buffer(), array->shapeInfo(), array->specialBuffer(),
array->specialShapeInfo(), arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillTruncatedNormal(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
double mean, double stdev) {
ExtraArguments arguments({mean, stdev});
PointersManager pm(context, "fillTruncatedNormal");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(
context, random::TruncatedNormalDistribution, &rng, array->buffer(), array->shapeInfo(), array->specialBuffer(),
array->specialShapeInfo(), array->buffer(), array->shapeInfo(), array->specialBuffer(), array->specialShapeInfo(),
array->buffer(), array->shapeInfo(), array->specialBuffer(), array->specialShapeInfo(),
arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
void RandomLauncher::fillBinomial(LaunchContext* context, graph::RandomGenerator& rng, NDArray* array,
int trials, double prob) {
ExtraArguments arguments({(double)trials, prob});
PointersManager pm(context, "fillBinomial");
NDArray::prepareSpecialUse({array}, {});
NativeOpExecutioner::execRandom(context, random::BinomialDistributionEx, &rng, array->buffer(), array->shapeInfo(),
array->specialBuffer(), array->specialShapeInfo(), array->buffer(),
array->shapeInfo(), array->specialBuffer(), array->specialShapeInfo(),
array->buffer(), array->shapeInfo(), array->specialBuffer(),
array->specialShapeInfo(), arguments.argumentsAsT(array->dataType()));
pm.synchronize();
NDArray::registerSpecialUse({array}, {});
}
} // namespace sd
@@ -0,0 +1,54 @@
/*
* ******************************************************************************
* *
* *
* * 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
* *****************************************************************************
*/
#include <helpers/ShapeBufferCreatorHelper.h>
#include <helpers/ShapeBufferPlatformHelper.h>
#include <helpers/cpu/CpuShapeBufferCreator.h>
#include <stdexcept>
namespace sd {
// Initialize static member
ShapeBufferCreator* ShapeBufferCreatorHelper::currentCreator_ = nullptr;
ShapeBufferCreator& ShapeBufferCreatorHelper::getCurrentCreator() {
// This prevents SIGSEGV crash when getCurrentCreator() is called before initialization
if (currentCreator_ == nullptr) {
// Trigger platform initialization which will call setCurrentCreator()
ShapeBufferPlatformHelper::initialize();
// Double-check after initialization attempt
if (currentCreator_ == nullptr) {
THROW_EXCEPTION("FATAL: ShapeBufferCreator not initialized! "
"ShapeBufferPlatformHelper::initialize() failed to set currentCreator_. "
"This indicates a critical initialization order bug.");
}
}
return *currentCreator_;
}
void ShapeBufferCreatorHelper::setCurrentCreator(ShapeBufferCreator* creator) {
if (creator == nullptr) {
THROW_EXCEPTION("ShapeBufferCreator cannot be null");
}
currentCreator_ = creator;
}
} // namespace sd
@@ -0,0 +1,81 @@
/*
* ******************************************************************************
* *
* *
* * 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
* *****************************************************************************
*/
#include <helpers/ShapeBufferPlatformHelper.h>
#include <helpers/cpu/CpuShapeBufferCreator.h>
#include <mutex>
// Include platform-specific headers conditionally
#if defined(SD_CUDA)
#include <helpers/cuda/CudaShapeBufferCreator.h>
#include <cuda.h>
#include <cuda_runtime.h>
#endif
// Forward declare Environment class if it's used for platform detection
namespace sd {
// This ensures ShapeBufferPlatformHelper is initialized BEFORE DirectShapeTrie or any other
// code tries to use it. The dummy struct with static member forces initialization at program startup.
struct ShapeBufferInitializer {
ShapeBufferInitializer() {
ShapeBufferPlatformHelper::initialize();
}
};
static ShapeBufferInitializer _force_early_init;
void ShapeBufferPlatformHelper::initialize() {
// Thread-safe initialization using static local mutex
// This prevents race conditions when multiple threads call initialize() simultaneously
static std::mutex init_mutex;
static bool init_done = false;
// Fast path: if already initialized, return immediately without locking
if (init_done) {
return;
}
// Slow path: acquire lock and check again
std::lock_guard<std::mutex> lock(init_mutex);
if (init_done) {
return; // Another thread completed initialization while we were waiting
}
#if defined(SD_CUDA)
printf("Initializing CUDA platform\n");
fflush(stdout);
// Switch to CUDA implementation
ShapeBufferCreatorHelper::setCurrentCreator(&CudaShapeBufferCreator::getInstance());
#else
printf("Initializing CPU platform\n");
fflush(stdout);
ShapeBufferCreatorHelper::setCurrentCreator(&CpuShapeBufferCreator::getInstance());
#endif
// Mark as complete - must be last line after all initialization
init_done = true;
// Add other platforms as needed (ROCm, OpenCL, etc.)
}
} // namespace sd
@@ -0,0 +1,308 @@
/* ******************************************************************************
*
*
* 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
//
#include <helpers/ShapeBuilders.h>
#include "array/ShapeDescriptor.h"
namespace sd {
LongType* ShapeBuilders::createShapeInfoFrom(ShapeDescriptor* descriptor) {
LongType bufferLen = shape::shapeInfoLength(descriptor->rank());
auto ret = new LongType[bufferLen];
ret[0] = descriptor->rank();
if(descriptor->rank() > 0) {
shape::setShape(ret, descriptor->shape_strides());
shape::setStrideConst(ret, descriptor->stridesPtr());
shape::setOrder(ret, descriptor->order());
} else {
std::vector<LongType> shape = {0};
std::vector<LongType> strides = {1};
shape::setShape(ret,shape.data());
shape::setStrideConst(ret, strides.data());
shape::setOrder(ret,'c');
}
shape::setExtra(ret, descriptor->extra());
if(ArrayOptions::dataType(ret) != descriptor->dataType()) {
ArrayOptions::setDataType(ret, descriptor->dataType());
}
return ret;
}
LongType* ShapeBuilders::createScalarShapeInfo(const DataType dataType, memory::Workspace* workspace) {
// there is no reason for shape info to use workspaces. we have constant shape helper for this
// workspaces with shapebuffers also appears to cause issues when reused elsewhere.
LongType lenOfShapeInfo = 6;
auto newShape = new LongType[lenOfShapeInfo];
newShape[0] = 0;
newShape[1] = 0;
newShape[2] = 1;
newShape[3] = ArrayOptions::setDataTypeValue(ArrayOptions::defaultFlag(), dataType);
newShape[4] = 1;
newShape[5] = 99;
DataType actualType = ArrayOptions::dataType(newShape);
if (actualType != dataType) {
printf("ERROR: Data type mismatch in scalarShapeInfo - requested %d but got %d\n",
DataTypeUtils::asInt(dataType), DataTypeUtils::asInt(actualType));
}
return newShape;
}
LongType* ShapeBuilders::createVectorShapeInfo(const DataType dataType, const LongType length,
memory::Workspace* workspace) {
//there is no reason for shape info to use workspaces. we have constant shape helper for this
// workspaces with shapebuffers also appears to cause issues when reused elsewhere.
LongType* newShape = new LongType[shape::shapeInfoLength(static_cast<LongType>(1))];
newShape[0] = 1;
newShape[1] = length;
newShape[2] = 1;
newShape[3] = ArrayOptions::setDataTypeValue(ArrayOptions::defaultFlag(), dataType);
newShape[4] = 1;
newShape[5] = 99;
return newShape;
}
LongType* ShapeBuilders::createShapeInfo(const DataType dataType, const char order, int rank,
const LongType* shapeOnly,
const LongType *strideOnly,
memory::Workspace* workspace, sd::LongType extras) {
LongType* shapeInfo = nullptr;
if (rank == 0) { // scalar case
shapeInfo = createScalarShapeInfo(dataType, workspace);
} else {
shapeInfo = new LongType[shape::shapeInfoLength(rank)];
// Initialize entire buffer to zero first
memset(shapeInfo, 0, shape::shapeInfoLength(rank) * sizeof(LongType));
shapeInfo[0] = rank;
// Set shape values
for (int i = 0; i < rank; i++) {
shapeInfo[i + 1] = shapeOnly[i];
}
// Set stride values
for (int i = 0; i < rank; i++) {
shapeInfo[i + 1 + rank] = strideOnly[i];
}
// Explicitly set EWS to -1 (unused) at position length-2
shapeInfo[shape::shapeInfoLength(rank) - 2] = -1;
// Set order (at position length-1)
shapeInfo[shape::shapeInfoLength(rank) - 1] = order;
}
// The 'extras' parameter may not have data type flags set, which would cause
// ArrayOptions::dataType() to return UNKNOWN, triggering validation errors.
// We must call setDataType() AFTER setExtra() to ensure the data type is correct.
ArrayOptions::setExtra(shapeInfo, extras);
ArrayOptions::setDataType(shapeInfo, dataType); // Ensure data type is set from the dataType parameter
shape::setOrder(shapeInfo, order);
return shapeInfo;
}
LongType* ShapeBuilders::copyShapeInfoWithNewType(const LongType* inShapeInfo, const DataType newType) {
int rank = shape::rank(inShapeInfo);
LongType* newShapeInfo = new LongType[shape::shapeInfoLength(rank)];
// Copy the basic shape structure
memcpy(newShapeInfo, inShapeInfo, shape::shapeInfoByteLength(inShapeInfo));
// Update the data type while preserving other properties
LongType currentExtra = ArrayOptions::extra(inShapeInfo);
LongType newExtra = ArrayOptions::setDataTypeValue(
ArrayOptions::propertyWithoutDataTypeValue(currentExtra),
newType
);
ArrayOptions::setExtra(newShapeInfo, newExtra);
return newShapeInfo;
}
////////////////////////////////////////////////////////////////////////////////
LongType * ShapeBuilders::createShapeInfo(const DataType dataType, const char order, int rank, const LongType* shapeOnly,
memory::Workspace* workspace, bool empty) {
LongType* shapeInfo = nullptr;
if (rank == 0) { // scalar case
shapeInfo = createScalarShapeInfo(dataType, workspace);
} else {
shapeInfo = new LongType[shape::shapeInfoLength(rank)];
shapeInfo[0] = rank;
for (int i = 0; i < rank; i++) {
shapeInfo[i + 1] = shapeOnly[i];
}
ArrayOptions::resetFlags(shapeInfo);
shape::updateStrides(shapeInfo, order, false);
}
ArrayOptions::setDataType(shapeInfo, dataType);
if (empty) {
ArrayOptions::setPropertyBit(shapeInfo, ARRAY_EMPTY);
}
return shapeInfo;
}
LongType* ShapeBuilders::emptyShapeInfoWithShape(const DataType dataType, std::vector<LongType>& shape,
memory::Workspace* workspace) {
auto shapeInfo = createShapeInfo(dataType, 'c', shape, workspace);
ArrayOptions::setPropertyBit(shapeInfo, ARRAY_EMPTY);
return shapeInfo;
}
LongType* ShapeBuilders::emptyShapeInfo(const DataType dataType, memory::Workspace* workspace) {
auto shapeInfo = createScalarShapeInfo(dataType, workspace);
ArrayOptions::setPropertyBit(shapeInfo, ARRAY_EMPTY);
return shapeInfo;
}
LongType* ShapeBuilders::emptyShapeInfo(const DataType dataType, const char order,
const std::vector<LongType>& shape, memory::Workspace* workspace) {
auto shapeInfo = createShapeInfo(dataType, order, shape.size(), shape.data(), workspace, true);
return shapeInfo;
}
LongType* ShapeBuilders::emptyShapeInfo(const DataType dataType, const char order, int rank,
const LongType* shapeOnly, memory::Workspace* workspace) {
auto shapeInfo2 = new LongType[shape::shapeInfoLength(rank)];
shapeInfo2[0] = rank;
for(int i = 0; i < rank; i++) {
shapeInfo2[i + 1] = shapeOnly[i];
//all empty strides are zero
shapeInfo2[i + 1 + rank] = 0;
}
shape::setOrder(shapeInfo2, order);
ArrayOptions::setPropertyBits(shapeInfo2, {ARRAY_EMPTY,ArrayOptions::flagForDataType(dataType)});
return shapeInfo2;
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::createShapeInfo(const DataType dataType, const char order,
const std::vector<LongType>& shapeOnly, memory::Workspace* workspace) {
bool isEmpty = false;
//shape size 1 but 0 can be scalar
if(shapeOnly.size() > 1)
for(size_t i = 0; i < shapeOnly.size(); i++) {
if(shapeOnly[i] == 0) {
isEmpty = true;
break;
}
}
auto ret = createShapeInfo(dataType, order, shapeOnly.size(), shapeOnly.data(), workspace, isEmpty);
if(isEmpty && !ArrayOptions::hasPropertyBitSet(ret, ARRAY_EMPTY)) {
THROW_EXCEPTION("Shape builders: empty was specified was true but shape info returned false");
} else if(!isEmpty && ArrayOptions::hasPropertyBitSet(ret, ARRAY_EMPTY)) {
THROW_EXCEPTION("Shape builders: empty was specified was false but shape info returned true");
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::createShapeInfo(const DataType dataType, const char order,
const std::initializer_list<LongType>& shapeOnly,
memory::Workspace* workspace) {
return createShapeInfo(dataType, order, std::vector<LongType>(shapeOnly), workspace);
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::copyShapeInfo(const LongType* inShapeInfo, const bool copyStrides,
memory::Workspace* workspace) {
LongType* outShapeInfo = new LongType[shape::shapeInfoLength(shape::rank(inShapeInfo))];
memcpy(outShapeInfo, inShapeInfo, shape::shapeInfoByteLength(inShapeInfo));
if (!copyStrides) shape::updateStrides(outShapeInfo, shape::order(outShapeInfo), false);
return outShapeInfo;
}
LongType* ShapeBuilders::setAsView(const LongType* inShapeInfo) {
LongType* outShapeInfo = copyShapeInfo(inShapeInfo, true, nullptr);
ArrayOptions::toggleIsView(outShapeInfo);
return outShapeInfo;
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::copyShapeInfoAndType(const LongType* inShapeInfo, const DataType dtype,
const bool copyStrides, memory::Workspace* workspace) {
LongType* outShapeInfo = copyShapeInfo(inShapeInfo, copyStrides, workspace);
ArrayOptions::setExtra(outShapeInfo, ArrayOptions::propertyWithoutDataTypeValue(ArrayOptions::extra(inShapeInfo))); // set extra value to 0 (like in DataTypeEx::TypeEx
ArrayOptions::setDataType(outShapeInfo, dtype);
return outShapeInfo;
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::copyShapeInfoAndType(const LongType* inShapeInfo,
const LongType* shapeInfoToGetTypeFrom, const bool copyStrides,
memory::Workspace* workspace) {
return copyShapeInfoAndType(inShapeInfo, ArrayOptions::dataType(shapeInfoToGetTypeFrom), copyStrides,
workspace);
}
////////////////////////////////////////////////////////////////////////////////
LongType* ShapeBuilders::createSubArrShapeInfo(const LongType* inShapeInfo, const LongType* dims, const int dimsSize,
memory::Workspace* workspace) {
LongType* subArrShapeInfo = nullptr;
ALLOCATE(subArrShapeInfo, workspace, shape::shapeInfoLength(dimsSize), LongType);
subArrShapeInfo[0] = dimsSize; // rank
subArrShapeInfo[2 * dimsSize + 1] = 0;
ArrayOptions::copyDataType(subArrShapeInfo, inShapeInfo); // type
subArrShapeInfo[2 * dimsSize + 3] = shape::order(inShapeInfo); // order
LongType* shape = shape::shapeOf(subArrShapeInfo);
LongType* strides = shape::stride(subArrShapeInfo);
bool isEmpty = false;
for (int i = 0; i < dimsSize; ++i) {
shape[i] = shape::sizeAt(inShapeInfo, dims[i]);
if(shape[i] == 0) {
isEmpty = true;
}
strides[i] = shape::strideAt(inShapeInfo, dims[i]);
}
shape::checkStridesEwsAndOrder(subArrShapeInfo);
if(isEmpty)
ArrayOptions::togglePropertyBit(subArrShapeInfo, ARRAY_EMPTY);
return subArrShapeInfo;
}
} // namespace sd
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,66 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by raver on 8/29/2018.
//
#include <helpers/SimpleReadWriteLock.h>
namespace sd {
SimpleReadWriteLock::SimpleReadWriteLock(const SimpleReadWriteLock& other) {
_read_locks.store(other._read_locks.load());
_write_locks.store(other._write_locks.load());
}
SimpleReadWriteLock::SimpleReadWriteLock() {
_read_locks.store(0);
_write_locks.store(0);
}
void SimpleReadWriteLock::lockRead() {
_mutex.lock();
_read_locks++;
while (_write_locks.load() > 0) {
// just loop
}
_mutex.unlock();
}
void SimpleReadWriteLock::unlockRead() { _read_locks--; }
// write lock
void SimpleReadWriteLock::lockWrite() {
_mutex.lock();
_write_locks++;
while (_read_locks.load() > 0) {
// just loop
}
_mutex.unlock();
}
void SimpleReadWriteLock::unlockWrite() { _write_locks--; }
SimpleReadWriteLock& SimpleReadWriteLock::operator=(const SimpleReadWriteLock& other) {
if (this == &other) return *this;
this->_write_locks.store(other._write_locks.load());
this->_read_locks.store(other._read_locks.load());
return *this;
}
} // namespace sd
+335
View File
@@ -0,0 +1,335 @@
/* ******************************************************************************
*
*
* 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 Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/EigenValsAndVecs.h>
#include <helpers/FullPivLU.h>
#include <helpers/HessenbergAndSchur.h>
#include <helpers/MmulHelper.h>
#include <helpers/Sqrtm.h>
#include <ops/declarable/helpers/lup.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
static void sqrtmQuasiTrianDiag(NDArray& matrixT, NDArray& sqrtT) {
const int rows = matrixT.sizeAt(0);
for (int i = 0; i < rows; i++) {
if (i == rows - 1 || matrixT.t<T>(i + 1, i) == (T)0) {
const auto elemT = matrixT.t<T>(i, i);
if (elemT < (T)0)
THROW_EXCEPTION(
"ops::helpers::Sqrtm::sqrtmQuasiTrianDiag: can't take sqrt of negative diagonal element of T matrix !");
sqrtT.r<T>(i, i) = math::sd_sqrt<T, T>(elemT);
} else {
NDArray *esViewPtr = matrixT({i, i + 2, i, i + 2}, true);
EigenValsAndVecs<T> es(*esViewPtr); // es._Vecs {2,2,2}, es._Vals{2,2}
delete esViewPtr;
NDArray& vecs = es._Vecs;
NDArray& vals = es._Vals;
const T& vecsReal00 = vecs.t<T>(0, 0, 0);
const T& vecsImag00 = vecs.t<T>(0, 0, 1);
const T& vecsReal01 = vecs.t<T>(0, 1, 0);
const T& vecsImag01 = vecs.t<T>(0, 1, 1);
const T& vecsReal10 = vecs.t<T>(1, 0, 0);
const T& vecsImag10 = vecs.t<T>(1, 0, 1);
const T& vecsReal11 = vecs.t<T>(1, 1, 0);
const T& vecsImag11 = vecs.t<T>(1, 1, 1);
// es.eigenvalues().cwiseSqrt().asDiagonal()
T eigenValsSqrt[2][2];
eigenValsSqrt[0][0] = vals.t<T>(0, 0);
eigenValsSqrt[0][1] = vals.t<T>(0, 1);
eigenValsSqrt[1][0] = vals.t<T>(1, 0);
eigenValsSqrt[1][1] = vals.t<T>(1, 1);
EigenValsAndVecs<T>::sqrtComplexNum(eigenValsSqrt[0][0], eigenValsSqrt[0][1]);
EigenValsAndVecs<T>::sqrtComplexNum(eigenValsSqrt[1][0], eigenValsSqrt[1][1]);
// es.eigenvectors() * es.eigenvalues().cwiseSqrt().asDiagonal()
T vecsElem[2][2][2];
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal00, vecsImag00, eigenValsSqrt[0][0], eigenValsSqrt[0][1],
vecsElem[0][0][0], vecsElem[0][0][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal01, vecsImag01, eigenValsSqrt[1][0], eigenValsSqrt[1][1],
vecsElem[0][1][0], vecsElem[0][1][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal10, vecsImag10, eigenValsSqrt[0][0], eigenValsSqrt[0][1],
vecsElem[1][0][0], vecsElem[1][0][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal11, vecsImag11, eigenValsSqrt[1][0], eigenValsSqrt[1][1],
vecsElem[1][1][0], vecsElem[1][1][1]);
// es.eigenvectors().inverse()
T vecsElemInv[2][2][2];
T tempReal, tempImag, divisorReal, divisorImag;
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal00, vecsImag00, vecsReal11, vecsImag11, divisorReal,
divisorImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal01, vecsImag01, vecsReal10, vecsImag10, tempReal, tempImag);
divisorReal -= tempReal;
divisorImag -= tempImag;
EigenValsAndVecs<T>::divideComplexNums(vecsReal11, vecsImag11, divisorReal, divisorImag, vecsElemInv[0][0][0],
vecsElemInv[0][0][1]);
EigenValsAndVecs<T>::divideComplexNums(-vecsReal01, -vecsImag01, divisorReal, divisorImag, vecsElemInv[0][1][0],
vecsElemInv[0][1][1]);
EigenValsAndVecs<T>::divideComplexNums(-vecsReal10, -vecsImag10, divisorReal, divisorImag, vecsElemInv[1][0][0],
vecsElemInv[1][0][1]);
EigenValsAndVecs<T>::divideComplexNums(vecsReal00, vecsImag00, divisorReal, divisorImag, vecsElemInv[1][1][0],
vecsElemInv[1][1][1]);
// result
T result[2][2][2];
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][0][0], vecsElem[0][0][1], vecsElemInv[0][0][0],
vecsElemInv[0][0][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][1][0], vecsElem[0][1][1], vecsElemInv[1][0][0],
vecsElemInv[1][0][1], result[0][0][0], result[0][0][1]);
result[0][0][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][0][0], vecsElem[0][0][1], vecsElemInv[0][1][0],
vecsElemInv[0][1][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][1][0], vecsElem[0][1][1], vecsElemInv[1][1][0],
vecsElemInv[1][1][1], result[0][1][0], result[0][1][1]);
result[0][1][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][0][0], vecsElem[1][0][1], vecsElemInv[0][0][0],
vecsElemInv[0][0][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][1][0], vecsElem[1][1][1], vecsElemInv[1][0][0],
vecsElemInv[1][0][1], result[1][0][0], result[1][0][1]);
result[1][0][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][0][0], vecsElem[1][0][1], vecsElemInv[0][1][0],
vecsElemInv[0][1][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][1][0], vecsElem[1][1][1], vecsElemInv[1][1][0],
vecsElemInv[1][1][1], result[1][1][0], result[1][1][1]);
result[1][1][0] += tempReal;
sqrtT.r<T>(i, i) = result[0][0][0];
sqrtT.r<T>(i, i + 1) = result[0][1][0];
sqrtT.r<T>(i + 1, i) = result[1][0][0];
sqrtT.r<T>(i + 1, i + 1) = result[1][1][0];
++i;
}
}
}
//////////////////////////////////////////////////////////////////////////
// all matrices are {2,2} here
template <typename T>
static void sqrtmQuasiTrianAuxEq(NDArray& A, NDArray& B, NDArray& C, NDArray& X) {
std::vector<LongType> tempShape = {4,4};
NDArray tempMatrix(A.ordering(),tempShape, A.dataType(), A.getContext());
tempMatrix.r<T>(0, 0) = A.t<T>(0, 0) + B.t<T>(0, 0);
tempMatrix.r<T>(1, 1) = A.t<T>(0, 0) + B.t<T>(1, 1);
tempMatrix.r<T>(2, 2) = A.t<T>(1, 1) + B.t<T>(0, 0);
tempMatrix.r<T>(3, 3) = A.t<T>(1, 1) + B.t<T>(1, 1);
tempMatrix.r<T>(0, 1) = B.t<T>(1, 0);
tempMatrix.r<T>(0, 2) = A.t<T>(0, 1);
tempMatrix.r<T>(1, 0) = B.t<T>(0, 1);
tempMatrix.r<T>(1, 3) = A.t<T>(0, 1);
tempMatrix.r<T>(2, 0) = A.t<T>(1, 0);
tempMatrix.r<T>(2, 3) = B.t<T>(1, 0);
tempMatrix.r<T>(3, 1) = A.t<T>(1, 0);
tempMatrix.r<T>(3, 2) = B.t<T>(0, 1);
tempMatrix.r<T>(0, 3) = (T)0;
tempMatrix.r<T>(1, 2) = (T)0;
tempMatrix.r<T>(2, 1) = (T)0;
tempMatrix.r<T>(3, 0) = (T)0;
std::vector<LongType> resultShape = {4,1};
NDArray result(A.ordering(), resultShape, A.dataType(), A.getContext());
result.r<T>(0, 0) = C.t<T>(0, 0);
result.r<T>(1, 0) = C.t<T>(0, 1);
result.r<T>(2, 0) = C.t<T>(1, 0);
result.r<T>(3, 0) = C.t<T>(1, 1);
FullPivLU<T>::solve(tempMatrix, result, result);
X.r<T>(0, 0) = result.t<T>(0);
X.r<T>(0, 1) = result.t<T>(1);
X.r<T>(1, 0) = result.t<T>(2);
X.r<T>(1, 1) = result.t<T>(3);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
static void sqrtmQuasiTrianOffDiag(NDArray& matrixT, NDArray& sqrtT) {
const int rows = matrixT.sizeAt(0);
for (int j = 1; j < rows; j++) {
if (matrixT.t<T>(j, j - 1) != (T)0) continue;
for (int i = j - 1; i >= 0; i--) {
if (i > 0 && matrixT.t<T>(i, i - 1) != (T)0) continue;
const bool iBlockIs2x2 = (i < rows - 1) && (matrixT.t<T>(i + 1, i) != (T)0);
const bool jBlockIs2x2 = (j < rows - 1) && (matrixT.t<T>(j + 1, j) != (T)0);
if (iBlockIs2x2 && jBlockIs2x2) {
NDArray *APtr = sqrtT({i, i + 2, i, i + 2}, true);
NDArray A = *APtr;
delete APtr;
NDArray *BPtr = sqrtT({j, j + 2, j, j + 2}, true);
NDArray B = *BPtr;
delete BPtr;
NDArray *XPtr = matrixT({i, i + 2, j, j + 2}, true);
NDArray X = *XPtr;
delete XPtr;
if (j - i > 2) {
NDArray *leftPtr = sqrtT({i, i + 2, i + 2, j}, true);
NDArray *rightPtr = sqrtT({i + 2, j, j, j + 2}, true);
auto mul = mmul(*leftPtr, *rightPtr);
X -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
sqrtmQuasiTrianAuxEq<T>(A, B, X, X);
sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 2, j, j + 2}, true);
assignPtr->assign(&X);
delete assignPtr;
} else if (iBlockIs2x2 && !jBlockIs2x2) {
NDArray *rhsPtr = matrixT({i, i + 2, j, j + 1}, true);
NDArray rhs = *rhsPtr;
delete rhsPtr;
if (j - i > 2) {
NDArray *leftPtr = sqrtT({i, i + 2, i + 2, j}, true);
NDArray *rightPtr = sqrtT({i + 2, j, j, j + 1}, true);
auto mul = mmul(*leftPtr, *rightPtr);
rhs -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
std::vector<LongType> aShape = {2,2};
NDArray A(matrixT.ordering(), aShape, matrixT.dataType(), matrixT.getContext());
A.r<T>(0, 0) = A.r<T>(1, 1) = sqrtT.t<T>(j, j);
A.r<T>(0, 1) = A.r<T>(1, 0) = T(0);
NDArray *addPtr = sqrtT({i, i + 2, i, i + 2}, true);
A += *addPtr;
delete addPtr;
FullPivLU<T>::solve(A, rhs, rhs);
// sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 2, j, j + 1}, true);
assignPtr->assign(&rhs);
delete assignPtr;
} else if (!iBlockIs2x2 && jBlockIs2x2) {
NDArray *rhsPtr = matrixT({i, i + 1, j, j + 2}, true);
NDArray rhs = *rhsPtr;
delete rhsPtr;
if (j - i > 1) {
NDArray *leftPtr = sqrtT({i, i + 1, i + 1, j}, true);
NDArray *rightPtr = sqrtT({i + 1, j, j, j + 2}, true);
auto mul = mmul(*leftPtr, *rightPtr);
rhs -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
std::vector<LongType> aShape = {2,2};
NDArray A(matrixT.ordering(),aShape, matrixT.dataType(), matrixT.getContext());
A.r<T>(0, 0) = A.r<T>(1, 1) = sqrtT.t<T>(i, i);
A.r<T>(0, 1) = A.r<T>(1, 0) = T(0);
NDArray *addPtr = sqrtT({j, j + 2, j, j + 2}, true);
NDArray *add = addPtr->transpose();
delete addPtr;
A += *add;
delete add;
NDArray *rhsT = rhs.transpose();
FullPivLU<T>::solve(A, *rhsT, *rhsT);
// sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 1, j, j + 2}, true);
assignPtr->assign(&rhs);
delete assignPtr;
delete rhsT;
} else if (!iBlockIs2x2 && !jBlockIs2x2) {
NDArray *leftPtr = sqrtT({i, i + 1, i + 1, j});
NDArray *rightPtr = sqrtT({i + 1, j, j, j + 1});
auto mul = mmul(*leftPtr, *rightPtr);
T temp = mul->t<T>(0); // dot
delete leftPtr;
delete rightPtr;
delete mul;
sqrtT.r<T>(i, j) = (matrixT.t<T>(i, j) - temp) / (sqrtT.t<T>(i, i) + sqrtT.t<T>(j, j));
}
}
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Sqrtm<T>::calc(NDArray& in, NDArray& out) {
if (in.rankOf() != 2 || in.sizeAt(0) != in.sizeAt(1))
THROW_EXCEPTION("ops::helpers::Sqrtm::calc: input matrix must have rank 2 and be square !");
if (!out.isSameShape(in))
THROW_EXCEPTION("ops::helpers::Sqrtm::calc: output matrix must have the same shape as input one!");
if (in.lengthOf() == 1) {
out.r<T>(0) = math::sd_sqrt<T, T>(in.t<T>(0));
return;
}
Schur<T> schur(in);
NDArray *inULike = in.ulike();
NDArray sqrtT = *inULike;
sqrtT.nullify();
sqrtmQuasiTrianDiag<T>(*schur.t, sqrtT);
sqrtmQuasiTrianOffDiag<T>(*schur.t, sqrtT);
NDArray *second = schur.u->transpose();
// out = U * sqrtT * U^T;
NDArray *temp = mmul(sqrtT, *second);
MmulHelper::mmul(schur.u, temp, &out);
delete inULike;
delete second;
delete temp;
}
BUILD_SINGLE_TEMPLATE( class Sqrtm, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
@@ -0,0 +1,357 @@
/*
* ******************************************************************************
* *
* *
* * 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
* *****************************************************************************
*/
//
// Created by raver119 on 20/04/18.
// @author Oleg Semeniv <oleg.semeniv@gmail.com>
//
#include <exceptions/datatype_exception.h>
#include <helpers/BitwiseUtils.h>
#include <helpers/StringUtils.h>
#include <bitset>
#include "execution/Threads.h"
#include "helpers/ShapeUtils.h"
namespace sd {
void StringUtils::setValueForDifferentDataType(NDArray* arr, LongType idx, NDArray* input, DataType zType) {
switch(zType) {
#if HAS_UTF8
case UTF8: {
switch(input->dataType()) {
case UTF8:
arr->p<std::string>(idx, input->e<std::string>(idx));
break;
case UTF16:
arr->p<std::string>(idx, std::string(input->e<std::u16string>(idx).begin(), input->e<std::u16string>(idx).end()));
break;
case UTF32:
arr->p<std::string>(idx, std::string(input->e<std::u32string>(idx).begin(), input->e<std::u32string>(idx).end()));
break;
default:
THROW_EXCEPTION("Unsupported DataType for source string.");
}
break;
}
#endif
#if HAS_UTF16
case UTF16: {
switch(input->dataType()) {
case UTF8:
arr->p<std::u16string>(idx, std::u16string(input->e<std::string>(idx).begin(), input->e<std::string>(idx).end()));
break;
case UTF16:
arr->p<std::u16string>(idx, input->e<std::u16string>(idx));
break;
case UTF32:
arr->p<std::u16string>(idx, std::u16string(input->e<std::u32string>(idx).begin(), input->e<std::u32string>(idx).end()));
break;
default:
THROW_EXCEPTION("Unsupported DataType for source string.");
}
break;
}
#endif
#if HAS_UTF32
case UTF32: {
switch(input->dataType()) {
case UTF8:
arr->p<std::u32string>(idx, std::u32string(input->e<std::string>(idx).begin(), input->e<std::string>(idx).end()));
break;
case UTF16:
arr->p<std::u32string>(idx, std::u32string(input->e<std::u16string>(idx).begin(), input->e<std::u16string>(idx).end()));
break;
case UTF32:
arr->p<std::u32string>(idx, input->e<std::u32string>(idx));
break;
default:
THROW_EXCEPTION("Unsupported DataType for source string.");
}
break;
}
#endif
default:
THROW_EXCEPTION("Unsupported DataType for destination string.");
}
}
void StringUtils::broadcastStringAssign(NDArray* x, NDArray* z) {
if (!x->isBroadcastableTo(*z)) {
THROW_EXCEPTION("Shapes of x and z are not broadcastable.");
}
auto zType = z->dataType();
auto xCasted = x->cast(zType);
std::vector<LongType> zeroVec = {0};
std::vector<LongType> *restDims = ShapeUtils::evalDimsToExclude(x->rankOf(), 1, zeroVec.data());
auto xTensors = xCasted->allTensorsAlongDimension(*restDims);
auto zTensors = z->allTensorsAlongDimension(*restDims);
delete restDims;
if (xCasted->isScalar()) {
for (int e = 0; e < zTensors.size(); e++) {
for (int f = 0; f < zTensors.at(e)->lengthOf(); f++) {
setValueForDifferentDataType(zTensors.at(e), f, xCasted, zType);
}
}
} else {
for (int e = 0; e < xTensors.size(); e++) {
auto tensor = xTensors.at(e);
for (int f = 0; f < tensor->lengthOf(); f++) {
setValueForDifferentDataType(zTensors.at(e), f, tensor, zType);
}
}
}
}
template <typename T>
void StringUtils::convertStringsForDifferentDataType(NDArray* sourceArray, NDArray* targetArray) {
if (!sourceArray->isS() || !targetArray->isS()) THROW_EXCEPTION("Source or target array is not a string array!");
int numStrings = sourceArray->isScalar() ? 1 : sourceArray->lengthOf();
auto inData = sourceArray->bufferAsT<int8_t>() + ShapeUtils::stringBufferHeaderRequirements(sourceArray->lengthOf());
auto outData = targetArray->bufferAsT<int8_t>() + ShapeUtils::stringBufferHeaderRequirements(targetArray->lengthOf());
const auto nInputoffsets = sourceArray->bufferAsT<LongType>();
const auto nOutputoffsets = targetArray->bufferAsT<LongType>();
for (int e = 0; e < numStrings; e++) {
auto idata = inData + nInputoffsets[e];
auto cdata = outData + nOutputoffsets[e];
auto start = nInputoffsets[e];
auto end = nInputoffsets[e + 1];
// Convert based on target type (using UTF conversions)
if (DataTypeUtils::fromT<T>() == UTF16) {
if (sourceArray->dataType() == UTF8) {
unicode::utf8to16(idata, cdata, end);
} else if(sourceArray->dataType() == UTF32) {
unicode::utf32to16(idata, cdata, (end / sizeof(char32_t)));
}
} else if (DataTypeUtils::fromT<T>() == UTF32) {
if (sourceArray->dataType() == UTF8) {
unicode::utf8to32(idata, cdata, end);
} else if(sourceArray->dataType() == UTF16) {
unicode::utf16to32(idata, cdata, (end / sizeof(char16_t)));
}
} else {
if (sourceArray->dataType() == UTF16) {
unicode::utf16to8(idata, cdata, (end / sizeof(char16_t)));
} else if(sourceArray->dataType() == UTF32) {
unicode::utf32to8(idata, cdata, (end / sizeof(char32_t)));
}
}
}
}
#define DEFINE_CONVERT(T) template void StringUtils::convertStringsForDifferentDataType<GET_SECOND(T)>(NDArray* sourceArray, NDArray* targetArray);
ITERATE_LIST((SD_STRING_TYPES),DEFINE_CONVERT)
template <typename T>
std::vector<LongType> StringUtils::calculateOffsetsForTargetDataType(NDArray* sourceArray) {
if (!sourceArray->isS()) THROW_EXCEPTION("Source array is not a string array!");
LongType offsetsLength = ShapeUtils::stringBufferHeaderRequirements(sourceArray->lengthOf());
std::vector<LongType> offsets(sourceArray->lengthOf() + 1);
const auto nInputoffsets = sourceArray->bufferAsT<LongType>();
LongType start = 0, stop = 0;
LongType dataLength = 0;
int numStrings = sourceArray->isScalar() ? 1 : sourceArray->lengthOf();
auto data = sourceArray->bufferAsT<int8_t>() + offsetsLength;
for (LongType e = 0; e < numStrings; e++) {
offsets[e] = dataLength;
start = nInputoffsets[e];
stop = nInputoffsets[e + 1];
// Determine size difference based on the target type (using UTF conversions)
if (sourceArray->dataType() == UTF8) {
dataLength += (DataTypeUtils::fromT<T>() == UTF16)
? unicode::offsetUtf8StringInUtf16(data + start, stop)
: unicode::offsetUtf8StringInUtf32(data + start, stop);
} else if (sourceArray->dataType() == UTF16) {
dataLength += (DataTypeUtils::fromT<T>() == UTF32)
? unicode::offsetUtf16StringInUtf32(data + start, (stop / sizeof(char16_t)))
: unicode::offsetUtf16StringInUtf8(data + start, (stop / sizeof(char16_t)));
} else if (sourceArray->dataType() == UTF32) {
dataLength += (DataTypeUtils::fromT<T>() == UTF16)
? unicode::offsetUtf32StringInUtf16(data + start, (stop / sizeof(char32_t)))
: unicode::offsetUtf32StringInUtf8(data + start, (stop / sizeof(char32_t)));
}
}
offsets[numStrings] = dataLength;
return offsets;
}
#define DEFINE_OFFSET(T) template std::vector<LongType> StringUtils::calculateOffsetsForTargetDataType<GET_SECOND(T)>(NDArray* sourceArray);
ITERATE_LIST((SD_STRING_TYPES),DEFINE_OFFSET)
static SD_INLINE bool match(const LongType* haystack, const LongType* needle, LongType length) {
for (int e = 0; e < length; e++)
if (haystack[e] != needle[e]) return false;
return true;
}
template <typename T>
std::string StringUtils::bitsToString(T value) {
return std::bitset<sizeof(T) * 8>(value).to_string();
}
template std::string StringUtils::bitsToString(int value);
template std::string StringUtils::bitsToString(uint32_t value);
template std::string StringUtils::bitsToString(LongType value);
template std::string StringUtils::bitsToString(uint64_t value);
LongType StringUtils::countSubarrays(const void* haystack, LongType haystackLength, const void* needle,
LongType needleLength) {
auto haystack2 = reinterpret_cast<const LongType*>(haystack);
auto needle2 = reinterpret_cast<const LongType*>(needle);
LongType number = 0;
for (LongType e = 0; e < haystackLength - needleLength; e++) {
if (match(&haystack2[e], needle2, needleLength)) number++;
}
return number;
}
LongType StringUtils::byteLength(NDArray& array) {
if (!array.isS())
THROW_EXCEPTION(datatype_exception::build("StringUtils::byteLength expects one of String types;", array.dataType()).what());
auto buffer = array.bufferAsT<LongType>();
return buffer[array.lengthOf()];
}
std::vector<std::string> StringUtils::split(const std::string& haystack, const std::string& delimiter) {
std::vector<std::string> output;
std::string::size_type prev_pos = 0, pos = 0;
// iterating through the haystack till the end
while ((pos = haystack.find(delimiter, pos)) != std::string::npos) {
output.emplace_back(haystack.substr(prev_pos, pos - prev_pos));
prev_pos = ++pos;
}
output.emplace_back(haystack.substr(prev_pos, pos - prev_pos)); // Last word
return output;
}
bool StringUtils::u8StringToU16String(const std::string& u8, std::u16string& u16) {
if (u8.empty()) return false;
u16.resize(unicode::offsetUtf8StringInUtf16(u8.data(), u8.size()) / sizeof(char16_t));
if (u8.size() == u16.size())
u16.assign(u8.begin(), u8.end());
else
return unicode::utf8to16(u8.data(), &u16[0], u8.size());
return true;
}
bool StringUtils::u8StringToU32String(const std::string& u8, std::u32string& u32) {
if (u8.empty()) return false;
u32.resize(unicode::offsetUtf8StringInUtf32(u8.data(), u8.size()) / sizeof(char32_t));
if (u8.size() == u32.size())
u32.assign(u8.begin(), u8.end());
else
return unicode::utf8to32(u8.data(), &u32[0], u8.size());
return true;
}
bool StringUtils::u16StringToU32String(const std::u16string& u16, std::u32string& u32) {
if (u16.empty()) return false;
u32.resize(unicode::offsetUtf16StringInUtf32(u16.data(), u16.size()) / sizeof(char32_t));
if (u16.size() == u32.size())
u32.assign(u16.begin(), u16.end());
else
return unicode::utf16to32(u16.data(), &u32[0], u16.size());
return true;
}
bool StringUtils::u16StringToU8String(const std::u16string& u16, std::string& u8) {
if (u16.empty()) return false;
u8.resize(unicode::offsetUtf16StringInUtf8(u16.data(), u16.size()));
if (u16.size() == u8.size())
u8.assign(u16.begin(), u16.end());
else
return unicode::utf16to8(u16.data(), &u8[0], u16.size());
return true;
}
bool StringUtils::u32StringToU16String(const std::u32string& u32, std::u16string& u16) {
if (u32.empty()) return false;
u16.resize(unicode::offsetUtf32StringInUtf16(u32.data(), u32.size()) / sizeof(char16_t));
if (u32.size() == u16.size())
u16.assign(u32.begin(), u32.end());
else
return unicode::utf32to16(u32.data(), &u16[0], u32.size());
return true;
}
bool StringUtils::u32StringToU8String(const std::u32string& u32, std::string& u8) {
if (u32.empty()) return false;
u8.resize(unicode::offsetUtf32StringInUtf8(u32.data(), u32.size()));
if (u32.size() == u8.size())
u8.assign(u32.begin(), u32.end());
else
return unicode::utf32to8(u32.data(), &u8[0], u32.size());
return true;
}
template <typename T>
std::string StringUtils::vectorToString(const std::vector<T>& vec) {
std::string result;
for (auto v : vec) result += valueToString<T>(v);
return result;
}
template std::string StringUtils::vectorToString(const std::vector<int>& vec);
template std::string StringUtils::vectorToString(const std::vector<LongType>& vec);
template std::string StringUtils::vectorToString(const std::vector<int16_t>& vec);
template std::string StringUtils::vectorToString(const std::vector<uint32_t>& vec);
} // namespace sd
+25
View File
@@ -0,0 +1,25 @@
/* ******************************************************************************
*
*
* 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 Adam Gibson
//
namespace shape {}
@@ -0,0 +1,180 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by Yurii Shyrma on 18.12.2017
//
#include <helpers/biDiagonalUp.h>
#include <helpers/householder.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
BiDiagonalUp::BiDiagonalUp(NDArray& matrix)
: _HHmatrix(matrix.dataType(), matrix.getContext(), true),
_HHbidiag(matrix.dataType(), matrix.getContext(), true),
_hhCoeffs(matrix.dataType(), matrix.getContext(), true) {
// input validation
if (matrix.rankOf() != 2 || matrix.isScalar())
THROW_EXCEPTION("ops::helpers::biDiagonalizeUp constructor: input array must be 2D matrix !");
std::vector<LongType> shape = {matrix.sizeAt(0), matrix.sizeAt(1)};
_HHmatrix = NDArray(matrix.ordering(), shape, matrix.dataType(), matrix.getContext());
std::vector<sd::LongType> shape2 = {matrix.sizeAt(1), matrix.sizeAt(1)};
_HHbidiag = NDArray(matrix.ordering(),shape2, matrix.dataType(), matrix.getContext());
_HHmatrix.assign(&matrix);
double zeroAssign = 0.;
_HHbidiag.assign(zeroAssign);
evalData();
}
template <typename T>
void BiDiagonalUp::_evalData() {
const auto rows = _HHmatrix.sizeAt(0);
const auto cols = _HHmatrix.sizeAt(1);
if (rows < cols)
THROW_EXCEPTION(
"ops::helpers::BiDiagonalizeUp::evalData method: this procedure is applicable only for input matrix with rows "
">= cols !");
T coeff, normX;
T x, y;
for (LongType i = 0; i < cols - 1; ++i) {
// evaluate Householder matrix nullifying columns
NDArray *column1Ptr = _HHmatrix({i, rows, i, i + 1});
NDArray column1 = *column1Ptr;
delete column1Ptr;
x = _HHmatrix.t<T>(i, i);
y = _HHbidiag.t<T>(i, i);
Householder<T>::evalHHmatrixDataI(column1, x, y);
_HHmatrix.r<T>(i, i) = x;
_HHbidiag.r<T>(i, i) = y;
// multiply corresponding matrix block on householder matrix from the left: P * bottomRightCorner
NDArray *bottomRightCorner1Ptr = _HHmatrix({i, rows, i + 1, cols}, true); // {i, cols}
NDArray bottomRightCorner1 = *bottomRightCorner1Ptr;
delete bottomRightCorner1Ptr;
NDArray *hhViewPtr = _HHmatrix({i + 1, rows, i, i + 1}, true);
Householder<T>::mulLeft(bottomRightCorner1, *hhViewPtr, _HHmatrix.t<T>(i, i));
delete hhViewPtr;
if (i == cols - 2) continue; // do not apply right multiplying at last iteration
// evaluate Householder matrix nullifying rows
NDArray *row1Ptr = _HHmatrix({i, i + 1, i + 1, cols});
NDArray row1 = *row1Ptr;
delete row1Ptr;
x = _HHmatrix.t<T>(i, i + 1);
y = _HHbidiag.t<T>(i, i + 1);
Householder<T>::evalHHmatrixDataI(row1, x, y);
_HHmatrix.r<T>(i, i + 1) = x;
_HHbidiag.r<T>(i, i + 1) = y;
// multiply corresponding matrix block on householder matrix from the right: bottomRightCorner * P
NDArray *bottomRightCorner2Ptr = _HHmatrix({i + 1, rows, i + 1, cols}, true); // {i, rows}
NDArray bottomRightCorner2 = *bottomRightCorner2Ptr;
delete bottomRightCorner2Ptr;
NDArray *hhView2Ptr = _HHmatrix({i, i + 1, i + 2, cols}, true);
Householder<T>::mulRight(bottomRightCorner2, *hhView2Ptr, _HHmatrix.t<T>(i, i + 1));
delete hhView2Ptr;
}
NDArray *row2Ptr = _HHmatrix({cols - 2, cols - 1, cols - 1, cols});
NDArray row2 = *row2Ptr;
delete row2Ptr;
x = _HHmatrix.t<T>(cols - 2, cols - 1);
y = _HHbidiag.t<T>(cols - 2, cols - 1);
Householder<T>::evalHHmatrixDataI(row2, x, y);
_HHmatrix.r<T>(cols - 2, cols - 1) = x;
_HHbidiag.r<T>(cols - 2, cols - 1) = y;
NDArray *column2Ptr = _HHmatrix({cols - 1, rows, cols - 1, cols});
NDArray column2 = *column2Ptr;
delete column2Ptr;
x = _HHmatrix.t<T>(cols - 1, cols - 1);
y = _HHbidiag.t<T>(cols - 1, cols - 1);
Householder<T>::evalHHmatrixDataI(column2, x, y);
_HHmatrix.r<T>(cols - 1, cols - 1) = x;
_HHbidiag.r<T>(cols - 1, cols - 1) = y;
}
//////////////////////////////////////////////////////////////////////////
void BiDiagonalUp::evalData() {
auto xType = _HHmatrix.dataType();
BUILD_SINGLE_SELECTOR(xType, _evalData, ();, SD_FLOAT_TYPES);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
HHsequence BiDiagonalUp::makeHHsequence_(const char type) {
const int diagSize = type == 'u' ? _HHbidiag.sizeAt(0) : _HHbidiag.sizeAt(0) - 1;
std::vector<LongType> shape = {diagSize};
_hhCoeffs = NDArray(_HHmatrix.ordering(),shape, _HHmatrix.dataType(), _HHmatrix.getContext());
if (type == 'u')
for (int i = 0; i < diagSize; ++i) _hhCoeffs.r<T>(i) = _HHmatrix.t<T>(i, i);
else
for (int i = 0; i < diagSize; ++i) _hhCoeffs.r<T>(i) = _HHmatrix.t<T>(i, i + 1);
HHsequence result(&_HHmatrix, &_hhCoeffs, type);
if (type != 'u') {
result._diagSize = diagSize;
result._shift = 1;
}
return result;
}
//////////////////////////////////////////////////////////////////////////
HHsequence BiDiagonalUp::makeHHsequence(const char type) {
auto xType = _HHmatrix.dataType();
BUILD_SINGLE_SELECTOR(xType, return makeHHsequence_, (type);, SD_FLOAT_TYPES);
// This should never be reached - BUILD_SINGLE_SELECTOR covers all SD_FLOAT_TYPES
THROW_EXCEPTION("BiDiagonalUp::makeHHsequence: unsupported data type");
}
BUILD_SINGLE_TEMPLATE( void BiDiagonalUp::_evalData, (), SD_FLOAT_TYPES);
BUILD_SINGLE_TEMPLATE( HHsequence BiDiagonalUp::makeHHsequence_, (const char type), SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
@@ -0,0 +1,253 @@
/* ******************************************************************************
*
* Copyright (c) 2024 Konduit K.K.
* 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.
*
* 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
******************************************************************************/
#include <helpers/generic/TypedTrie.h>
#include "array/ConstantShapeBuffer.h"
#include "array/TadPack.h"
#include "helpers/DirectTadTrie.h"
namespace sd {
namespace generic {
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
struct TypedTrie<KeyType, ValueType, NUM_STRIPES>::TrieNode {
std::shared_ptr<ValueType> value;
std::unordered_map<typename KeyType::value_type, std::unique_ptr<TrieNode>> children;
std::atomic<bool> isComplete{false};
std::chrono::steady_clock::time_point lastAccess;
TrieNode() : lastAccess(std::chrono::steady_clock::now()) {}
};
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
TypedTrie<KeyType, ValueType, NUM_STRIPES>::TypedTrie()
{
#ifdef __cpp_exceptions
try {
for (auto& root : _roots) {
root = std::make_unique<TrieNode>();
_resourceManager.registerNode();
}
} catch (...) {
cleanup();
throw;
}
#else
// Exceptions disabled - direct initialization without try/catch
for (auto& root : _roots) {
root = std::make_unique<TrieNode>();
_resourceManager.registerNode();
}
#endif
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
TypedTrie<KeyType, ValueType, NUM_STRIPES>::~TypedTrie() {
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
size_t TypedTrie<KeyType, ValueType, NUM_STRIPES>::getStripeIndex(const KeyType& key) const {
size_t h = 0;
for (const auto& elem : key) {
h = h * 31 + std::hash<typename KeyType::value_type>{}(elem);
}
return h & (NUM_STRIPES - 1);
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
typename TypedTrie<KeyType, ValueType, NUM_STRIPES>::TrieNode*
TypedTrie<KeyType, ValueType, NUM_STRIPES>::findOrCreateNode(TrieNode* root,
const KeyType& key,
bool createIfMissing) const {
if (!root) return nullptr;
TrieNode* current = root;
for (const auto& k : key) {
auto it = current->children.find(k);
if (it == current->children.end()) {
if (!createIfMissing) return nullptr;
auto newNode = std::make_unique<TrieNode>();
if (!newNode) return nullptr;
current->children[k] = std::move(newNode);
}
current = current->children[k].get();
if (!current) return nullptr;
}
return current;
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
void TypedTrie<KeyType, ValueType, NUM_STRIPES>::cleanupNode(TrieNode* node) {
if (!node) return;
auto now = std::chrono::steady_clock::now();
auto age = std::chrono::duration_cast<std::chrono::minutes>(
now - node->lastAccess).count();
// Clean up nodes older than 30 minutes
if (age > 30) {
node->value.reset();
for (auto it = node->children.begin(); it != node->children.end();) {
auto* child = it->second.get();
if (child) {
cleanupNode(child);
if (!child->value && child->children.empty()) {
_resourceManager.unregisterNode();
it = node->children.erase(it);
continue;
}
}
++it;
}
}
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
std::shared_ptr<ValueType> TypedTrie<KeyType, ValueType, NUM_STRIPES>::get(const KeyType& key) const {
if (!key.empty()) {
auto scope = _resourceManager.createScope();
size_t stripe = getStripeIndex(key);
_locks.lockStripe(stripe, false);
auto node = findOrCreateNode(_roots[stripe].get(), key, false);
if (node && node->isComplete.load(std::memory_order_acquire)) {
auto result = node->value;
if (result) {
node->lastAccess = std::chrono::steady_clock::now();
_locks.unlockStripe(stripe, false);
return result;
}
}
_locks.unlockStripe(stripe, false);
}
return nullptr;
}
// Previously instantiated TypedTrie with ConstantShapeBuffer* (raw pointer) as ValueType
// This caused undefined behavior by mixing raw pointer storage with shared_ptr returns
// The correct instantiation uses std::shared_ptr<ConstantShapeBuffer> (line 246)
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
bool TypedTrie<KeyType, ValueType, NUM_STRIPES>::insert(const KeyType& key,
std::shared_ptr<ValueType> value) {
if (!value || key.empty()) return false;
auto scope = _resourceManager.createScope();
size_t stripe = getStripeIndex(key);
_locks.lockStripe(stripe, true);
auto node = findOrCreateNode(_roots[stripe].get(), key, true);
if (!node) {
_locks.unlockStripe(stripe, true);
return false;
}
if (node->value || node->isComplete.load(std::memory_order_acquire)) {
_locks.unlockStripe(stripe, true);
return false;
}
node->value = value;
node->lastAccess = std::chrono::steady_clock::now();
node->isComplete.store(true, std::memory_order_release);
_locks.unlockStripe(stripe, true);
return true;
}
// template bool sd::generic::TypedTrie<..., ConstantShapeBuffer*, ...>::insert(...)
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
bool TypedTrie<KeyType, ValueType, NUM_STRIPES>::remove(const KeyType& key) {
auto scope = _resourceManager.createScope();
size_t stripe = getStripeIndex(key);
_locks.lockStripe(stripe, true);
auto node = findOrCreateNode(_roots[stripe].get(), key, false);
if (!node || !node->value) {
_locks.unlockStripe(stripe, true);
return false;
}
node->value.reset();
node->isComplete.store(false, std::memory_order_release);
_locks.unlockStripe(stripe, true);
return true;
}
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
void TypedTrie<KeyType, ValueType, NUM_STRIPES>::cleanup() {
auto scope = _resourceManager.createScope();
std::vector<size_t> stripes;
for (size_t i = 0; i < NUM_STRIPES; ++i) {
stripes.push_back(i);
}
auto guard = _locks.acquireMultiLock(stripes, true);
for (auto& root : _roots) {
if (root) cleanupNode(root.get());
}
}
// template void sd::generic::TypedTrie<..., ConstantShapeBuffer*, ...>::cleanup();
template void
sd::generic::TypedTrie<std::vector<long long, std::allocator<long long>>,
sd::TadPack*,
32>::cleanup();
// template sd::generic::TypedTrie<..., ConstantShapeBuffer*, ...>::~TypedTrie();
template<typename KeyType, typename ValueType, size_t NUM_STRIPES>
typename TypedTrie<KeyType, ValueType, NUM_STRIPES>::Stats
TypedTrie<KeyType, ValueType, NUM_STRIPES>::getStats() const {
Stats stats;
for (size_t i = 0; i < NUM_STRIPES; ++i) {
stats.stripeCounts[i] = _locks.getStripeCount(i);
}
return stats;
}
} // namespace generic
} // namespace sd
template class sd::generic::TypedTrie<std::vector<unsigned char>, std::shared_ptr<sd::ConstantShapeBuffer>, 32>;
template class sd::generic::TypedTrie<std::vector<sd::LongType>, std::shared_ptr<sd::TadPack>, 32>;
// NOTE: TadPack raw pointer instantiations kept for now (may need similar cleanup in future)
template std::shared_ptr<sd::TadPack*>
sd::generic::TypedTrie<std::vector<long long, std::allocator<long long>>,
sd::TadPack*,
32>::get(const std::vector<long long, std::allocator<long long>>& key) const;
template bool
sd::generic::TypedTrie<std::vector<long long, std::allocator<long long>>,
sd::TadPack*,
32>::insert(const std::vector<long long, std::allocator<long long>>& key,
std::shared_ptr<sd::TadPack*> value);
template
sd::generic::TypedTrie<std::vector<long long, std::allocator<long long>>,
sd::TadPack*,
32>::~TypedTrie();
// template sd::generic::TypedTrie<..., ConstantShapeBuffer*, ...>::TypedTrie();
// This was mixing raw pointer storage with shared_ptr-based lifecycle management
@@ -0,0 +1,71 @@
/* ******************************************************************************
*
*
* 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
//
#include <helpers/helper_hash.h>
#include <helpers/logger.h>
namespace sd {
namespace ops {
HashHelper& HashHelper::getInstance() {
static HashHelper instance;
return instance;
}
LongType HashHelper::getLongHash(std::string& str) {
_locker.lock();
if (!_isInit) {
sd_verbose("Building HashUtil table\n", "");
unsigned long long h = 0x544B2FBACAAF1684L;
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 31; j++) {
h = (((unsigned long long)h) >> 7) ^ h;
h = (h << 11) ^ h;
h = (((unsigned long long)h) >> 10) ^ h;
}
_byteTable[i] = h;
}
_isInit = true;
}
_locker.unlock();
//note: DO NOT change this type.
//when something like thread sanitizer + cuda is used
//the offsets can get absurdly big.
//you get errors like: left shift of 11 places cannot be represented in type
unsigned long long h = HSTART;
unsigned long long hmult = HMULT;
LongType len = str.size();
for (int i = 0; i < len; i++) {
char ch = str.at(i);
auto uch = (unsigned char)ch;
h = (h * hmult) ^ _byteTable[ch & 0xff];
h = (h * hmult) ^ _byteTable[(uch >> 8) & 0xff];
}
return h;
}
} // namespace ops
} // namespace sd
+175
View File
@@ -0,0 +1,175 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by Yurii Shyrma on 11.01.2018
//
#include <helpers/hhColPivQR.h>
#include <helpers/householder.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
HHcolPivQR::HHcolPivQR(NDArray &matrix) {
_qr = matrix.dup(matrix.ordering());
std::vector<LongType> coeffsShape = {1,_diagSize};
_diagSize = math::sd_min<int>(matrix.sizeAt(0), matrix.sizeAt(1));
std::vector<LongType> permShape = {matrix.sizeAt(1), matrix.sizeAt(1)};
_coeffs = new NDArray(matrix.ordering(),coeffsShape, matrix.dataType(), matrix.getContext());
_permut = new NDArray(matrix.ordering(), permShape, matrix.dataType(), matrix.getContext());
evalData();
}
void HHcolPivQR::evalData() { BUILD_SINGLE_SELECTOR(_qr->dataType(), _evalData, (), SD_FLOAT_TYPES); }
//////////////////////////////////////////////////////////////////////////
template <typename T>
void HHcolPivQR::_evalData() {
const int rows = _qr->sizeAt(0);
const int cols = _qr->sizeAt(1);
std::vector<LongType> colsShape = {cols};
NDArray transp(_qr->ordering(), colsShape, _qr->dataType(), _qr->getContext());
NDArray normsUpd(_qr->ordering(), colsShape , _qr->dataType(), _qr->getContext());
NDArray normsDir(_qr->ordering(),colsShape , _qr->dataType(), _qr->getContext());
auto qRDeRef = *_qr;
for (int k = 0; k < cols; ++k) {
NDArray *colViewPtr = qRDeRef({0, 0, k, k + 1});
auto norm = colViewPtr->reduceNumber(reduce::Norm2);
normsDir.r<T>(k) = normsUpd.r<T>(k) = norm->t<T>(0);
delete norm;
delete colViewPtr;
}
auto max = (normsUpd.reduceNumber(reduce::Max));
T normScaled = max->t<T>(0) * DataTypeUtils::eps<T>();
T threshold1 = normScaled * normScaled / (T)rows;
T threshold2 = math::sd_sqrt<T, T>(DataTypeUtils::eps<T>());
T nonZeroPivots = static_cast<T>(_diagSize);
T maxPivot = static_cast<T>(0.);
delete max;
for (int k = 0; k < _diagSize; ++k) {
NDArray *normsUpdViewPtr = normsUpd({k, -1});
NDArray *indexNum = normsUpdViewPtr->indexReduceNumber(indexreduce::IndexMax);
int biggestColIndex = indexNum->e<int>(0);
auto max2 = normsUpdViewPtr->reduceNumber(reduce::Max);
T biggestColNorm = max2->t<T>(0);
delete normsUpdViewPtr;
delete max2;
T biggestColSqNorm = biggestColNorm * biggestColNorm;
biggestColIndex += k;
if (nonZeroPivots == (T)_diagSize && biggestColSqNorm < threshold1 * (T)(rows - k)) nonZeroPivots = k;
transp.r<T>(k) = (T)biggestColIndex;
if (k != biggestColIndex) {
NDArray *temp1Ptr = qRDeRef({0, 0, k, k + 1});
NDArray temp1 = *temp1Ptr;
delete temp1Ptr;
NDArray *temp2Ptr = qRDeRef({0, 0, biggestColIndex, biggestColIndex + 1});
NDArray temp2 = *temp2Ptr;
delete temp2Ptr;
temp1.swapUnsafe(temp2);
math::sd_swap<T>(normsUpd.r<T>(k), normsUpd.r<T>(biggestColIndex));
math::sd_swap<T>(normsDir.r<T>(k), normsDir.r<T>(biggestColIndex));
}
T normX, c;
NDArray *qrBlockPtr = qRDeRef({k, rows, k, k + 1});
NDArray qrBlock = *qrBlockPtr;
delete qrBlockPtr;
Householder<T>::evalHHmatrixDataI(qrBlock, c, normX);
_coeffs->r<T>(k) = c;
_qr->r<T>(k, k) = normX;
T max = math::sd_abs<T,T>(normX);
if (max > maxPivot) maxPivot = max;
if (k < rows && (k + 1) < cols) {
NDArray *qrBlock2Ptr = qRDeRef({k, rows, k + 1, cols}, true);
NDArray qrBlock2 = *qrBlock2Ptr;
delete qrBlock2Ptr;
NDArray *tailPtr = qRDeRef({k + 1, rows, k, k + 1}, true);
NDArray tail = *tailPtr;
delete tailPtr;
Householder<T>::mulLeft(qrBlock2, tail, _coeffs->t<T>(k));
}
for (int j = k + 1; j < cols; ++j) {
if (normsUpd.t<T>(j) != (T)0.f) {
T temp = math::sd_abs<T,T>(_qr->t<T>(k, j)) / normsUpd.t<T>(j);
temp = ((T)1. + temp) * ((T)1. - temp);
temp = temp < (T)0. ? (T)0. : temp;
T temp2 = temp * normsUpd.t<T>(j) * normsUpd.t<T>(j) / (normsDir.t<T>(j) * normsDir.t<T>(j));
if (temp2 <= threshold2) {
if (k + 1 < rows && j < cols) {
NDArray *normViewPtr = qRDeRef({k + 1, rows, j, j + 1});
auto reduce = normViewPtr->reduceNumber(reduce::Norm2);
normsDir.r<T>(j) = reduce->t<T>(0);
delete normViewPtr;
delete reduce;
}
normsUpd.r<T>(j) = normsDir.t<T>(j);
} else
normsUpd.r<T>(j) = normsUpd.t<T>(j) * math::sd_sqrt<T, T>(temp);
}
}
delete indexNum;
}
_permut->setIdentity();
auto permuteRef = *_permut;
for (int k = 0; k < _diagSize; ++k) {
int idx = transp.e<int>(k);
NDArray *temp1Ptr = permuteRef({0, 0, k, k + 1});
NDArray temp1 = *temp1Ptr;
delete temp1Ptr;
NDArray *temp2Ptr = permuteRef({0, 0, idx, idx + 1});
NDArray temp2 = *temp2Ptr;
delete temp2Ptr;
temp1.swapUnsafe(temp2);
}
}
BUILD_SINGLE_TEMPLATE( void HHcolPivQR::_evalData, (), SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
+137
View File
@@ -0,0 +1,137 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by Yurii Shyrma on 02.01.2018
//
#include <helpers/hhSequence.h>
#include <helpers/householder.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
HHsequence::HHsequence(NDArray* vectors, NDArray* coeffs, const char type)
: _vectors(vectors), _coeffs(coeffs) {
_diagSize = math::sd_min(_vectors->sizeAt(0), _vectors->sizeAt(1));
_shift = 0;
_type = type;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void HHsequence::mulLeft_(NDArray* matrix) {
const int rows = _vectors->sizeAt(0);
const int cols = _vectors->sizeAt(1);
const int inRows = matrix->sizeAt(0);
NDArray matrixRef = *matrix;
NDArray vectorsRef = *_vectors;
for (int i = _diagSize - 1; i >= 0; --i) {
if (_type == 'u') {
NDArray *blockPtr = matrixRef({inRows - rows + _shift + i, inRows, 0, 0}, true);
NDArray block = *blockPtr;
NDArray *vectorPtr = vectorsRef({i + 1 + _shift, rows, i, i + 1}, true);
NDArray vector = *vectorPtr;
Householder<T>::mulLeft(block, vector, _coeffs->t<T>(i));
delete blockPtr;
delete vectorPtr;
} else {
NDArray *blockPtr = matrixRef({inRows - cols + _shift + i, inRows, 0, 0}, true);
NDArray block = *blockPtr;
NDArray *vectorPtr = vectorsRef({i, i + 1, i + 1 + _shift, cols}, true);
NDArray vector = *vectorPtr;
Householder<T>::mulLeft(block, vector, _coeffs->t<T>(i));
delete blockPtr;
delete vectorPtr;
}
}
}
//////////////////////////////////////////////////////////////////////////
NDArray HHsequence::getTail(const int idx) const {
int first = idx + 1 + _shift;
NDArray vectorsRef = *_vectors;
if (_type == 'u') {
NDArray *tailPtr = vectorsRef({first, -1, idx, idx + 1}, true);
NDArray tail = *tailPtr;
delete tailPtr;
return tail;
} else {
NDArray *tailPtr = vectorsRef({idx, idx + 1, first, -1}, true);
NDArray tail = *tailPtr;
delete tailPtr;
return tail;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void HHsequence::applyTo_(NDArray* dest) {
int size = _type == 'u' ? _vectors->sizeAt(0) : _vectors->sizeAt(1);
NDArray *originalDest = dest;
NDArray destRef = *dest;
std::vector<LongType> sizeShape = {size,size};
if (dest->rankOf() != 2 || (dest->sizeAt(0) != size && dest->sizeAt(1) != size)) {
dest = new NDArray(dest->ordering(), sizeShape, dest->dataType(), dest->getContext());
destRef = *dest;
}
dest->setIdentity();
for (int k = _diagSize - 1; k >= 0; --k) {
int curNum = size - k - _shift;
if (curNum < 1 || (k + 1 + _shift) >= size) continue;
NDArray *blockPtr = destRef({dest->sizeAt(0) - curNum, dest->sizeAt(0), dest->sizeAt(1) - curNum, dest->sizeAt(1)}, true);
NDArray block = *blockPtr;
NDArray tailK = getTail(k);
Householder<T>::mulLeft(block, tailK, _coeffs->t<T>(k));
delete blockPtr;
}
if(originalDest != dest) {
delete dest;
}
}
//////////////////////////////////////////////////////////////////////////
void HHsequence::applyTo(NDArray* dest) {
auto xType = _coeffs->dataType();
BUILD_SINGLE_SELECTOR(xType, applyTo_, (dest), SD_FLOAT_TYPES);
}
//////////////////////////////////////////////////////////////////////////
void HHsequence::mulLeft(NDArray* matrix) {
auto xType = _coeffs->dataType();
BUILD_SINGLE_SELECTOR(xType, mulLeft_, (matrix), SD_FLOAT_TYPES);
}
BUILD_SINGLE_TEMPLATE( void HHsequence::applyTo_, (sd::NDArray * dest), SD_FLOAT_TYPES);
BUILD_SINGLE_TEMPLATE( void HHsequence::mulLeft_, (NDArray * matrix), SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
@@ -0,0 +1,230 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by Yurii Shyrma on 18.12.2017
//
#include <helpers/householder.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Householder<T>::evalHHmatrixData(NDArray& x, NDArray& tail, T& coeff, T& normX) {
// input validation
if (x.rankOf() != 1 && !x.isScalar())
THROW_EXCEPTION(
"ops::helpers::Householder::evalHHmatrixData method: input array must have rank = 1 or to be scalar!");
if (!x.isScalar() && x.lengthOf() != tail.lengthOf() + 1)
THROW_EXCEPTION(
"ops::helpers::Householder::evalHHmatrixData method: input tail vector must have length less than unity "
"compared to input x vector!");
const auto xLen = x.lengthOf();
NDArray *xTail = xLen > 1 ? x({1, -1}) : nullptr;
T tailXnorm;
if (xLen > 1) {
auto* tailNormPtr = xTail->reduceNumber(reduce::SquaredNorm);
tailXnorm = tailNormPtr->t<T>(0);
delete tailNormPtr;
} else {
tailXnorm = (T)0;
}
const auto xFirstElem = x.t<T>(0);
if (tailXnorm <= DataTypeUtils::min_positive<T>()) {
normX = xFirstElem;
coeff = (T)0.f;
tail = (T)0.f;
} else {
normX = math::sd_sqrt<T, T>(xFirstElem * xFirstElem + tailXnorm);
if (xFirstElem >= (T)0.f) normX = -normX; // choose opposite sign to lessen roundoff error
coeff = (normX - xFirstElem) / normX;
T divisor = xFirstElem - normX;
NDArray *tailAssign = (*xTail) / divisor;
tail.assign(tailAssign);
delete tailAssign;
}
if (xTail != nullptr) delete xTail;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Householder<T>::evalHHmatrixDataI(NDArray& x, T& coeff, T& normX) {
// input validation
if (x.rankOf() != 1 && !x.isScalar())
THROW_EXCEPTION(
"ops::helpers::Householder::evalHHmatrixDataI method: input array must have rank = 1 or to be scalar!");
int rows = (int)x.lengthOf() - 1;
int num = 1;
if (rows == 0) {
rows = 1;
num = 0;
}
NDArray *tailPtr = x({num, -1});
NDArray tail = *tailPtr;
delete tailPtr;
evalHHmatrixData(x, tail, coeff, normX);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Householder<T>::mulLeft(NDArray& matrix, NDArray& tail, const T coeff) {
if (matrix.sizeAt(0) == 1 && coeff != (T)0) {
NDArray *scaledResult = matrix * ((T)1.f - coeff);
matrix.assign(scaledResult);
delete scaledResult;
} else if (coeff != (T)0.f) {
NDArray *bottomPartPtr = matrix({1, matrix.sizeAt(0), 0, 0}, true);
NDArray bottomPart = *bottomPartPtr;
delete bottomPartPtr;
NDArray *fistRowPtr = matrix({0, 1, 0, 0}, true);
NDArray fistRow = *fistRowPtr;
delete fistRowPtr;
NDArray *tailTranspose = tail.transpose();
if (tail.isColumnVector()) {
NDArray *resultingRow = mmul(*tailTranspose, bottomPart);
NDArray *rowPlusFirst = (*resultingRow) + fistRow;
delete resultingRow;
resultingRow = rowPlusFirst;
NDArray *scaledRow = (*resultingRow) * coeff;
delete resultingRow;
resultingRow = scaledRow;
NDArray *firstMinusRow = fistRow - (*resultingRow);
fistRow.assign(firstMinusRow);
delete firstMinusRow;
NDArray *tailMulRow = mmul(tail, *resultingRow);
NDArray *bottomMinusTailMul = bottomPart - (*tailMulRow);
bottomPart.assign(bottomMinusTailMul);
delete tailMulRow;
delete bottomMinusTailMul;
delete resultingRow;
} else {
NDArray *resultingRow = mmul(tail, bottomPart);
NDArray *rowPlusFirst = (*resultingRow) + fistRow;
delete resultingRow;
resultingRow = rowPlusFirst;
NDArray *scaledRow = (*resultingRow) * coeff;
delete resultingRow;
resultingRow = scaledRow;
NDArray *firstMinusRow = fistRow - (*resultingRow);
fistRow.assign(firstMinusRow);
delete firstMinusRow;
NDArray *transTailMulRow = mmul(*tailTranspose, *resultingRow);
NDArray *bottomMinusTrans = bottomPart - (*transTailMulRow);
bottomPart.assign(bottomMinusTrans);
delete transTailMulRow;
delete bottomMinusTrans;
delete resultingRow;
}
delete tailTranspose;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Householder<T>::mulRight(NDArray& matrix, NDArray& tail, const T coeff) {
if (matrix.sizeAt(1) == 1 && coeff != (T)0) {
NDArray *scaledResult = matrix * ((T)1.f - coeff);
matrix.assign(scaledResult);
delete scaledResult;
} else if (coeff != (T)0.f) {
NDArray *rightPartPtr = matrix({0, 0, 1, matrix.sizeAt(1)}, true);
NDArray rightPart = *rightPartPtr;
delete rightPartPtr;
NDArray *fistColPtr = matrix({0, 0, 0, 1}, true);
NDArray fistCol = *fistColPtr;
delete fistColPtr;
NDArray *transposedTail = tail.transpose();
if (tail.isColumnVector()) {
NDArray *resultingCol = mmul(rightPart, tail);
NDArray *colPlusFirst = (*resultingCol) + fistCol;
delete resultingCol;
resultingCol = colPlusFirst;
NDArray *scaledCol = (*resultingCol) * coeff;
delete resultingCol;
resultingCol = scaledCol;
NDArray *firstMinusCol = fistCol - (*resultingCol);
fistCol.assign(firstMinusCol);
delete firstMinusCol;
NDArray *colMulTransTail = mmul(*resultingCol, *transposedTail);
NDArray *rightMinusColMul = rightPart - (*colMulTransTail);
rightPart.assign(rightMinusColMul);
delete colMulTransTail;
delete rightMinusColMul;
delete resultingCol;
} else {
NDArray *resultingCol = mmul(rightPart, *transposedTail);
NDArray *colPlusFirst = (*resultingCol) + fistCol;
delete resultingCol;
resultingCol = colPlusFirst;
NDArray *scaledCol = (*resultingCol) * coeff;
delete resultingCol;
resultingCol = scaledCol;
NDArray *firstMinusCol = fistCol - (*resultingCol);
fistCol.assign(firstMinusCol);
delete firstMinusCol;
NDArray *colMulTail = mmul(*resultingCol, tail);
NDArray *rightMinusColMul = rightPart - (*colMulTail);
rightPart.assign(rightMinusColMul);
delete colMulTail;
delete rightMinusColMul;
delete resultingCol;
}
delete transposedTail;
}
}
BUILD_SINGLE_TEMPLATE( class Householder, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
+503
View File
@@ -0,0 +1,503 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by Yurii Shyrma on 11.01.2018
//
#include <helpers/MmulHelper.h>
#include <helpers/hhColPivQR.h>
#include <helpers/jacobiSVD.h>
#if NOT_EXCLUDED(svd)
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
JacobiSVD<T>::JacobiSVD(NDArray& matrix, const bool calcU, const bool calcV, const bool fullUV)
: _m(matrix.dataType(), matrix.getContext(), true),
_s(matrix.dataType(), matrix.getContext(), true),
_u(matrix.dataType(), matrix.getContext(), true),
_v(matrix.dataType(), matrix.getContext(), true) {
if (matrix.rankOf() != 2 || matrix.isScalar())
THROW_EXCEPTION("ops::helpers::JacobiSVD constructor: input array must be 2D matrix !");
_rows = static_cast<int>(matrix.sizeAt(0));
_cols = static_cast<int>(matrix.sizeAt(1));
_diagSize = math::sd_min<int>(_rows, _cols);
_calcU = calcU;
_calcV = calcV;
_fullUV = fullUV;
std::vector<LongType> sShape = {_diagSize,1};
_s = NDArray(matrix.ordering(),sShape, matrix.dataType(), matrix.getContext());
if (_calcU) {
std::vector<LongType> rowsShape = {_rows,_rows};
std::vector<LongType> rowsShape2 = {_rows,_diagSize};
if (_fullUV)
_u = NDArray(matrix.ordering(), rowsShape, matrix.dataType(), matrix.getContext());
else
_u = NDArray(matrix.ordering(), rowsShape2, matrix.dataType(), matrix.getContext());
} else {
std::vector<LongType> rowsShape = {_rows, 1};
_u = NDArray(matrix.ordering(), rowsShape, matrix.dataType(), matrix.getContext());
}
if (_calcV) {
if (_fullUV) {
std::vector<LongType> colsShape = {_cols, _cols};
_v = NDArray(matrix.ordering(), colsShape, matrix.dataType(), matrix.getContext());
}
else {
std::vector<LongType> shape = {_cols, _diagSize};
_v = NDArray(matrix.ordering(),shape, matrix.dataType(), matrix.getContext());
}
} else {
std::vector<LongType> vShape = {_cols, 1};
_v = NDArray(matrix.ordering(), vShape, matrix.dataType(), matrix.getContext());
}
std::vector<LongType> mShape = {_diagSize, _diagSize};
_m = NDArray(matrix.ordering(), mShape, matrix.dataType(), matrix.getContext());
evalData(matrix);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void JacobiSVD<T>::mulRotationOnLeft(const int i, const int j, NDArray& block, NDArray& rotation) {
if (i < j) {
if (j + 1 > block.sizeAt(0))
THROW_EXCEPTION(
"ops::helpers::JacobiSVD mulRotationOnLeft: second arguments is out of array row range !");
NDArray *tempPtr = block({i, j + 1, j - i, 0, 0, 0}, true, true);
NDArray temp = *tempPtr;
NDArray *tempAssignResult = mmul(rotation, temp);
temp.assign(tempAssignResult);
delete tempAssignResult;
delete tempPtr;
} else {
if (j + 1 > block.sizeAt(0) || i + 1 > block.sizeAt(0))
THROW_EXCEPTION(
"ops::helpers::JacobiSVD mulRotationOnLeft: some or both integer arguments are out of array row range !");
std::vector<LongType> tempShape = {2, block.sizeAt(1)};
NDArray temp(block.ordering(),tempShape, block.dataType(), block.getContext());
NDArray *row1Ptr = block({i, i + 1, 0, 0}, true);
NDArray row1 = *row1Ptr;
NDArray *row2Ptr = block({j, j + 1, 0, 0}, true);
NDArray row2 = *row2Ptr;
NDArray *rowTemp1Ptr = temp({0, 1, 0, 0}, true);
NDArray rowTemp1 = *rowTemp1Ptr;
NDArray *rowTemp2Ptr = temp({1, 2, 0, 0}, true);
NDArray rowTemp2 = *rowTemp2Ptr;
rowTemp1.assign(&row1);
rowTemp2.assign(&row2);
NDArray *tempAssignResult = mmul(rotation, temp);
temp.assign(tempAssignResult);
delete tempAssignResult;
row1.assign(&rowTemp1);
row2.assign(&rowTemp2);
delete row1Ptr;
delete row2Ptr;
delete rowTemp1Ptr;
delete rowTemp2Ptr;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void JacobiSVD<T>::mulRotationOnRight(const int i, const int j, NDArray& block, NDArray& rotation) {
if (i < j) {
if (j + 1 > block.sizeAt(1))
THROW_EXCEPTION(
"ops::helpers::JacobiSVD mulRotationOnRight: second argument is out of array column range !");
NDArray *tempPtr = block({0, 0, 0, i, j + 1, j - i}, true, true);
NDArray temp = *tempPtr;
NDArray *tempAssignResult = mmul(temp, rotation);
temp.assign(tempAssignResult);
delete tempAssignResult;
delete tempPtr;
} else {
if (j + 1 > block.sizeAt(1) || i + 1 > block.sizeAt(1))
THROW_EXCEPTION(
"ops::helpers::JacobiSVD mulRotationOnRight: some or both integer arguments are out of array column range !");
std::vector<LongType> tempShape = {block.sizeAt(0), 2};
NDArray temp(block.ordering(), tempShape, block.dataType(), block.getContext());
NDArray *col1Ptr = block({0, 0, i, i + 1}, true);
NDArray col1 = *col1Ptr;
NDArray *col2Ptr = block({0, 0, j, j + 1}, true);
NDArray col2 = *col2Ptr;
NDArray *colTemp1Ptr = temp({0, 0, 0, 1}, true);
NDArray colTemp1 = *colTemp1Ptr;
NDArray *colTemp2Ptr = temp({0, 0, 1, 2}, true);
NDArray colTemp2 = *colTemp2Ptr;
colTemp1.assign(&col1);
colTemp2.assign(&col2);
NDArray *tempAssignResult = mmul(temp, rotation);
temp.assign(tempAssignResult);
delete tempAssignResult;
col1.assign(&colTemp1);
col2.assign(&colTemp2);
delete col1Ptr;
delete col2Ptr;
delete colTemp1Ptr;
delete colTemp2Ptr;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
bool JacobiSVD<T>::isBlock2x2NotDiag(NDArray& block, int p, int q, T& maxElem) {
std::vector<LongType> shape = {2, 2};
NDArray rotation(_m.ordering(), shape, _m.dataType(), _m.getContext());
T n = math::sd_sqrt<T, T>(block.t<T>(p, p) * block.t<T>(p, p) + block.t<T>(q, p) * block.t<T>(q, p));
const T almostZero = DataTypeUtils::min_positive<T>();
const T precision = DataTypeUtils::eps<T>();
if (n == (T)0.f) {
block.r<T>(p, p) = (T)0;
block.r<T>(q, p) = (T)0;
} else {
T v = block.t<T>(p, p) / n;
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = v;
v = block.t<T>(q, p) / n;
rotation.r<T>(0, 1) = v;
rotation.r<T>(1, 0) = -rotation.template t<T>(0, 1);
mulRotationOnLeft(p, q, block, rotation);
NDArray *rotT = rotation.transpose();
if (_calcU) mulRotationOnRight(p, q, _u, *rotT);
delete rotT;
}
maxElem =
math::sd_max<T>(maxElem, math::sd_max<T>(math::sd_abs<T,T>(block.t<T>(p, p)), math::sd_abs<T,T>(block.t<T>(q, q))));
T threshold = math::sd_max<T>(almostZero, precision * maxElem);
return math::sd_abs<T,T>(block.t<T>(p, q)) > threshold || math::sd_abs<T,T>(block.t<T>(q, p)) > threshold;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
bool JacobiSVD<T>::createJacobiRotation(const T& x, const T& y, const T& z, NDArray& rotation) {
T denom = (T)(2.f) * math::sd_abs<T,T>(y);
if (denom < DataTypeUtils::min_positive<T>()) {
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = (T)1.f;
rotation.r<T>(0, 1) = rotation.r<T>(1, 0) = (T)0.f;
return false;
} else {
T tau = (x - z) / denom;
T w = math::sd_sqrt<T, T>(tau * tau + (T)1.f);
T t;
if (tau > (T)0.)
t = (T)1.f / (tau + w);
else
t = (T)1.f / (tau - w);
T sign = t > (T)0. ? (T)1.f : (T)-1.f;
T cos = (T)1.f / math::sd_sqrt<T, T>(t * t + (T)1.f);
T sin = -sign * (y / math::sd_abs<T,T>(y)) * math::sd_abs<T,T>(t) * cos;
rotation.r<T>(0, 1) = sin;
rotation.r<T>(1, 0) = -sin;
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = cos;
return true;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void JacobiSVD<T>::createJacobiRotationGivens(const T& p, const T& q, NDArray& rotation) {
T cos, sin;
if (q == (T)0) {
cos = p < (T)0 ? (T)-1 : (T)1;
sin = (T)0;
} else if (p == (T)0) {
cos = (T)0;
sin = q < (T)0 ? (T)1 : (T)-1;
} else if (math::sd_abs<T,T>(p) > math::sd_abs<T,T>(q)) {
T t = q / p;
T u = math::sd_sqrt<T, T>((T)1 + t * t);
if (p < (T)0) u = -u;
cos = (T)1 / u;
sin = -t * cos;
} else {
T t = p / q;
T u = math::sd_sqrt<T, T>((T)1 + t * t);
if (q < (T)0) u = -u;
sin = -(T)1 / u;
cos = -t * sin;
}
rotation.r<T>(0, 1) = sin;
rotation.r<T>(1, 0) = -sin;
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = cos;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void JacobiSVD<T>::svd2x2(NDArray& block, int p, int q, NDArray& left, NDArray& right) {
std::vector<LongType> shape = {2, 2};
NDArray m(block.ordering(), shape, block.dataType(), block.getContext());
m.r<T>(0, 0) = block.t<T>(p, p);
m.r<T>(0, 1) = block.t<T>(p, q);
m.r<T>(1, 0) = block.t<T>(q, p);
m.r<T>(1, 1) = block.t<T>(q, q);
NDArray rotation(block.ordering(),shape, block.dataType(), block.getContext());
T t = m.t<T>(0, 0) + m.t<T>(1, 1);
T d = m.t<T>(1, 0) - m.t<T>(0, 1);
if (math::sd_abs<T,T>(d) < DataTypeUtils::min<T>()) {
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = (T)1;
rotation.r<T>(0, 1) = rotation.r<T>(1, 0) = (T)0;
} else {
T u = t / d;
T tmp = math::sd_sqrt<T, T>((T)1.f + u * u);
rotation.r<T>(0, 0) = rotation.r<T>(1, 1) = u / tmp;
rotation.r<T>(0, 1) = (T)1.f / tmp;
rotation.r<T>(1, 0) = -rotation.t<T>(0, 1);
}
NDArray *mAssignResult = mmul(rotation, m);
m.assign(mAssignResult);
delete mAssignResult;
createJacobiRotation(m.t<T>(0, 0), m.t<T>(0, 1), m.t<T>(1, 1), right);
NDArray *rightT = right.transpose();
NDArray *leftAssignResult = mmul(rotation, *rightT);
left.assign(leftAssignResult);
delete leftAssignResult;
delete rightT;
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void JacobiSVD<T>::evalData(NDArray& matrix) {
const T precision = (T)2.f * DataTypeUtils::eps<T>();
const T almostZero = DataTypeUtils::min_positive<T>();
auto* scaleResult = matrix.reduceNumber(reduce::AMax);
T scale = scaleResult->template t<T>(0);
delete scaleResult;
if (scale < (T)1.f) scale = (T)1.f;
if (_rows > _cols) {
NDArray *scaled = matrix / scale;
HHcolPivQR qr(*scaled);
delete scaled;
NDArray qrRef = *qr._qr;
NDArray *mAssignPtr = qrRef({0, _cols, 0, _cols});
NDArray mAssign = *mAssignPtr;
_m.assign(&mAssign);
delete mAssignPtr;
_m.fillAsTriangular<T>(0., 0, 0, _m, 'l',false);
HHsequence hhSeg(qr._qr, qr._coeffs, 'u');
if (_fullUV)
hhSeg.applyTo(&_u);
else if (_calcU) {
_u.setIdentity();
hhSeg.mulLeft(&_u);
}
if (_calcV) _v.assign(qr._permut);
} else if (_rows < _cols) {
NDArray *matrixT = matrix.transpose();
NDArray *scaled = (*matrixT) / scale;
HHcolPivQR qr(*scaled);
delete scaled;
NDArray qrRef = *qr._qr;
NDArray *mAssignPtr = qrRef({0, _rows, 0, _rows});
NDArray mAssign = *mAssignPtr;
_m.assign(&mAssign);
delete mAssignPtr;
_m.fillAsTriangular<T>(0., 0, 0, _m, 'l',false);
_m.transposei();
HHsequence hhSeg(qr._qr, qr._coeffs, 'u'); // type = 'u' is not mistake here !
if (_fullUV)
hhSeg.applyTo(&_v);
else if (_calcV) {
_v.setIdentity();
hhSeg.mulLeft(&_v);
}
if (_calcU) _u.assign(qr._permut);
delete matrixT;
} else {
NDArray *mAssignPtr = matrix({0, _diagSize, 0, _diagSize});
NDArray *mAssignDiv = (*mAssignPtr) / scale;
_m.assign(mAssignDiv);
delete mAssignDiv;
delete mAssignPtr;
if (_calcU) _u.setIdentity();
if (_calcV) _v.setIdentity();
}
T maxDiagElem = static_cast<T>(0.);
for (int i = 0; i < _diagSize; ++i) {
T current = math::sd_abs<T,T>(_m.t<T>(i, i));
if (maxDiagElem < current) maxDiagElem = current;
}
bool stop = false;
while (!stop) {
stop = true;
for (int p = 1; p < _diagSize; ++p) {
for (int q = 0; q < p; ++q) {
T threshold = math::sd_max<T>(almostZero, precision * maxDiagElem);
if (math::sd_abs<T,T>(_m.t<T>(p, q)) > threshold || math::sd_abs<T,T>(_m.t<T>(q, p)) > threshold) {
stop = false;
std::vector<LongType> shape = {2, 2};
NDArray rotLeft(_m.ordering(), shape, _m.dataType(), _m.getContext());
NDArray rotRight(_m.ordering(), shape, _m.dataType(), _m.getContext());
svd2x2(_m, p, q, rotLeft, rotRight);
mulRotationOnLeft(p, q, _m, rotLeft);
NDArray *rotLeftTranspose = rotLeft.transpose();
if (_calcU) mulRotationOnRight(p, q, _u, *rotLeftTranspose);
mulRotationOnRight(p, q, _m, rotRight);
if (_calcV) mulRotationOnRight(p, q, _v, rotRight);
maxDiagElem = math::sd_max<T>(
maxDiagElem, math::sd_max<T>(math::sd_abs<T,T>(_m.t<T>(p, p)), math::sd_abs<T,T>(_m.t<T>(q, q))));
delete rotLeftTranspose;
}
}
}
}
for (int i = 0; i < _diagSize; ++i) {
_s.r<T>(i) = math::sd_abs<T,T>(_m.t<T>(i, i));
if (_calcU && _m.t<T>(i, i) < (T)0.) {
NDArray *tempPtr = _u({0, 0, i, i + 1}, true);
NDArray temp = *tempPtr;
temp.applyTransform(transform::Neg, &temp, nullptr);
delete tempPtr;
}
}
_s *= scale;
for (int i = 0; i < _diagSize; i++) {
NDArray *sSlicePtr = _s({i, -1, 0, 0});
NDArray sSlice = *sSlicePtr;
NDArray *indexNum = sSlice.indexReduceNumber(indexreduce::IndexMax, nullptr);
int pos = indexNum->template e<int>(0);
auto* maxResult = sSlice.reduceNumber(reduce::Max);
T maxSingVal = maxResult->template t<T>(0);
delete maxResult;
delete sSlicePtr;
delete indexNum;
if (maxSingVal == (T)0.) break;
if (pos) {
pos += i;
math::sd_swap<T>(_s.r<T>(i), _s.r<T>(pos));
if (_calcU) {
NDArray *temp1Ptr = _u({0, 0, pos, pos + 1}, true);
NDArray temp1 = *temp1Ptr;
NDArray *temp2Ptr = _u({0, 0, i, i + 1}, true);
NDArray temp2 = *temp2Ptr;
temp1.swapUnsafe(temp2);
delete temp1Ptr;
delete temp2Ptr;
}
if (_calcV) {
NDArray *temp1Ptr = _v({0, 0, pos, pos + 1}, true);
NDArray temp1 = *temp1Ptr;
NDArray *temp2Ptr = _v({0, 0, i, i + 1}, true);
NDArray temp2 = *temp2Ptr;
temp1.swapUnsafe(temp2);
delete temp1Ptr;
delete temp2Ptr;
}
}
}
}
BUILD_SINGLE_TEMPLATE( class JacobiSVD, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd
#endif
+68
View File
@@ -0,0 +1,68 @@
/* ******************************************************************************
*
*
* 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
******************************************************************************/
//
// Created by raver119 on 31.10.2017.
//
#include <helpers/logger.h>
namespace sd {
SD_HOST void Logger::info(const char *format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
fflush(stdout);
}
SD_HOST void Logger::infoEmpty(const char *format) {
if(format != nullptr)
printf("%s",format);
}
SD_HOST void Logger::printv(const char *format, const std::vector<int> &vec) {
printf("%s: {", format);
for (size_t e = 0; e < vec.size(); e++) {
auto v = vec[e];
printf("%i", v);
if (e < vec.size() - 1) printf(", ");
}
printf("}\n");
fflush(stdout);
}
SD_HOST void Logger::printv(const char *format, const std::vector<LongType> &vec) {
printf("%s: {", format);
for (size_t e = 0; e < vec.size(); e++) {
auto v = vec[e];
printf("%lld", (long long)v);
if (e < vec.size() - 1) printf(", ");
}
printf("}\n");
fflush(stdout);
}
SD_HOST_DEVICE Status Logger::logStatusMsg(Status code, const char *msg) {
if (msg != nullptr) sd_printf("%s\n", msg);
return code;
}
SD_HOST_DEVICE Status Logger::logKernelFailureMsg(const char *msg) { return logStatusMsg(Status::KERNEL_FAILURE, msg); }
} // namespace sd
@@ -0,0 +1,147 @@
//
// Created by agibsonccc on 8/30/24.
//
#ifndef LIBND4J_RESHAPENOCOPY_H
#define LIBND4J_RESHAPENOCOPY_H
#include <system/op_boilerplate.h>
#include <helpers/reshapeNoCopy.h>
#include <helpers/shape.h>
#include <array/ArrayOptions.hXX>
namespace sd {
namespace ops {
namespace helpers {
bool reshapeNoAlloc(const sd::LongType* inShape,
const std::vector<sd::LongType>& newShape,
char order,
sd::LongType* outShape) {
LongType oldnd = shape::rank(inShape);
std::vector<sd::LongType> olddims(oldnd);
std::vector<sd::LongType> oldstrides(oldnd);
sd::LongType np, op, last_stride;
int oi, oj, ok, ni, nj, nk;
std::vector<sd::LongType> newStrides(newShape.size());
int newnd = newShape.size();
bool isFOrder = order == 'f';
// FIX: Set data type early, before any return statements
// This ensures data type is preserved even for empty arrays
if(ArrayOptions::numDataTypesSet(ArrayOptions::extra(outShape)) < 1) {
ArrayOptions::setDataType(outShape, ArrayOptions::dataType(inShape));
}
// Remove axes with dimension 1 from the old array
int actual_oldnd = 0;
for (oi = 0; oi < oldnd; oi++) {
if (shape::shapeOf(inShape)[oi] != 1) {
olddims[actual_oldnd] = shape::shapeOf(inShape)[oi];
oldstrides[actual_oldnd] = shape::stride(inShape)[oi];
actual_oldnd++;
}
}
oldnd = actual_oldnd;
np = 1;
for (ni = 0; ni < newnd; ni++) {
np *= newShape[ni];
}
op = 1;
for (oi = 0; oi < oldnd; oi++) {
op *= olddims[oi];
}
if (np != op) {
return false; // total sizes must match
}
if (np == 0) {
// FIX: Data type has already been set above, so empty arrays will have correct type
return false; // don't support empty arrays
}
// oi to oj and ni to nj give the axis ranges currently worked with
oi = 0;
oj = 1;
ni = 0;
nj = 1;
while (ni < newnd && oi < oldnd) {
np = newShape[ni];
op = olddims[oi];
while (np != op) {
if (np < op) {
np *= newShape[nj++];
} else {
op *= olddims[oj++];
}
}
// Check whether the original axes can be combined
for (ok = oi; ok < oj - 1; ok++) {
if (isFOrder) {
if (oldstrides[ok + 1] != olddims[ok] * oldstrides[ok]) {
return false; // not contiguous enough
}
} else {
// C order
if (oldstrides[ok] != olddims[ok + 1] * oldstrides[ok + 1]) {
return false; // not contiguous enough
}
}
}
// Calculate new strides for all axes currently worked with
if (isFOrder) {
newStrides[ni] = oldstrides[oi];
for (nk = ni + 1; nk < nj; nk++) {
newStrides[nk] = newStrides[nk - 1] * newShape[nk - 1];
}
} else {
// C order
newStrides[nj - 1] = oldstrides[oj - 1];
for (nk = nj - 1; nk > ni; nk--) {
newStrides[nk - 1] = newStrides[nk] * newShape[nk];
}
}
ni = nj++;
oi = oj++;
}
// Set strides corresponding to trailing 1s of the new shape
if (ni >= 1) {
last_stride = newStrides[ni - 1];
} else {
last_stride = 1;
}
if (isFOrder && ni >= 1) {
last_stride *= newShape[ni - 1];
}
for (nk = ni; nk < newnd; nk++) {
newStrides[nk] = last_stride;
}
// Update the output shape info
outShape[0] = newnd; // Set rank
shape::setShape(outShape, const_cast<sd::LongType*>(newShape.data()));
shape::setStride(outShape, newStrides.data());
// Set order first
shape::setOrder(outShape, order);
// Data type was set early (lines 28-32) but we set it again here as a defensive measure
// to ensure it's preserved even if other shape operations modified the extra field
ArrayOptions::setDataType(outShape, ArrayOptions::dataType(inShape));
return true;
}
}
}
}
#endif