Files
deeplearning4j--deeplearning4j/libnd4j/include/ops/declarable/helpers/cpu/sg_cb.cpp
T
2026-07-13 12:47:05 +08:00

1010 lines
37 KiB
C++

/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author raver119@gmail.com
//
#include <execution/Threads.h>
#include <ops/declarable/helpers/sg_cb.h>
#include <math/templatemath.h>
#define HS_MAX_EXP 6.0f
#include <cstddef>
#include <cstdlib>
#include <new>
namespace sd {
namespace ops {
namespace helpers {
template <typename T>
void hSoftmax_(T *vsyn0, T *vsyn1, T *vexpTable, T *vneu1e, const double alpha, const int vectorLength, const int code,
const int expLength, const bool isInference) {
auto syn0 = reinterpret_cast<T *>(vsyn0);
auto syn1 = reinterpret_cast<T *>(vsyn1);
auto expTable = reinterpret_cast<T *>(vexpTable);
auto neu1e = reinterpret_cast<T *>(vneu1e);
T dot(0.0f);
T g(0.0f);
T f(0.0f);
// dot
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
dot += syn0[e] * syn1[e];
}
// gradient
if (dot < (T)-HS_MAX_EXP || dot >= (T)HS_MAX_EXP) return;
int idx = static_cast<int>((dot + HS_MAX_EXP) * ((float)expLength / HS_MAX_EXP / 2.0f));
if (idx >= expLength || idx < 0) return;
f = expTable[idx];
g = (static_cast<T>(1.0f) - static_cast<T>(code) - f) * (T)alpha;
if(!isInference) {
PRAGMA_OMP_SIMD
for (int x = 0; x < vectorLength; x++) {
neu1e[x] += g * syn1[x];
syn1[x] += g * syn0[x];
}
} else {
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
neu1e[e] = g * syn1[e] + neu1e[e];
}
}
}
template <typename T>
void nSampling_(void *vsyn0, void *vsyn1Neg, void *vexpTable, void *vneu1e, double alpha, int vectorLength, int code,
int expLength, bool isInference) {
auto syn0 = reinterpret_cast<T *>(vsyn0);
auto syn1Neg = reinterpret_cast<T *>(vsyn1Neg);
auto expTable = reinterpret_cast<T *>(vexpTable);
auto neu1e = reinterpret_cast<T *>(vneu1e);
T dot = (T)0.0f;
T g = (T)0.0f;
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
dot += syn0[e] * syn1Neg[e];
}
if (dot > HS_MAX_EXP)
g = (code - 1) * alpha;
else if (dot < (T)-HS_MAX_EXP)
g = (code - 0) * alpha;
else {
int idx = (int)((dot + (T)HS_MAX_EXP) * ((T)expLength / HS_MAX_EXP / 2.0));
if (idx >= expLength) return;
if (idx < 0) return;
g = ((T)code - expTable[idx]) * alpha;
}
// axpy2
if (!isInference) {
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
neu1e[e] += g * syn1Neg[e];
syn1Neg[e] += g * syn0[e];
}
} else {
// axpy1
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
neu1e[e] += g * syn1Neg[e];
}
}
}
template <typename T>
void cbow_(NDArray &vsyn0, NDArray &vsyn1, NDArray &vsyn1Neg, NDArray &vexpTable, NDArray &vnegTable, NDArray &vinfVector, int target,
int ngStarter, int *context, int *lockedWords, int *indices, int *codes, double alpha,
sd::LongType randomValue, const int contextWidth, const int hsRounds, const int nsRounds,
const int vocabSize, const int vectorLength, const int expLength, const int negLength, const int numLabels,
const bool trainWords,double minLearningRate,const int iterations) {
auto syn0 = reinterpret_cast<T *>(vsyn0.bufferAsT<T>());
auto syn1 = reinterpret_cast<T *>(vsyn1.bufferAsT<T>());
auto syn1Neg = reinterpret_cast<T *>(vsyn1Neg.bufferAsT<T>());
auto expTable = reinterpret_cast<T *>(vexpTable.bufferAsT<T>());
auto negTable = reinterpret_cast<T *>(vnegTable.bufferAsT<T>());
auto infVector = reinterpret_cast<T *>(vinfVector.bufferAsT<T>());
auto neu1 = new T[vectorLength];
auto neu1e = new T[vectorLength];
memset(neu1, 0, vectorLength * sizeof(T));
memset(neu1e, 0, vectorLength * sizeof(T));
for(int i = 0; i < iterations; i++) {
// building neu1 for current window
for (int c = 0; c < contextWidth; c++) {
T *syn0word = syn0 + (context[c] * vectorLength);
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) {
neu1[i] += syn0word[i];
}
}
// for inference we add additional inference vector
if(infVector != nullptr && contextWidth > 0) {
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) {
neu1[i] = (infVector[i] + neu1[i]) / (contextWidth + 1);
}
} else if(infVector != nullptr && contextWidth > 0) {
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) {
neu1[i] = (infVector[i] + neu1[i]) / (contextWidth);
}
}
// softmax round
if (hsRounds > 0) {
for (int i = 0; i < hsRounds; i++) {
hSoftmax_<T>(neu1, syn1 + (indices[i] * vectorLength), expTable, neu1e, alpha, vectorLength, codes[i], expLength,
infVector != nullptr);
}
}
auto nsStarter = ngStarter;
auto irow = nsStarter;
if (nsRounds > 0) {
for (int r = 0; r < nsRounds + 1; r++) {
if (r == 0) {
// target is known in advance
} else {
randomValue = randomValue * (unsigned long long)25214903917 + 11;
auto idx = sd::math::sd_abs<sd::LongType,sd::LongType>((randomValue >> 16) % negLength);
irow = idx >= negLength ? -1 : static_cast<int>(negTable[idx]);
if (irow < 0 || irow >= vocabSize) irow = randomValue % (vocabSize - 1) + 1;
if (irow == nsStarter) continue;
}
auto syn1NegRow = syn1Neg + (irow * vectorLength);
nSampling_<T>(neu1, syn1NegRow, expTable, neu1e, alpha, vectorLength, r == 0 ? 1 : 0,
expLength, infVector != nullptr);
}
}
// if we don't train words - we skip start of idxSyn0
int starter = trainWords == 1 ? 0 : contextWidth - numLabels;
// propagate neu1e -> syn0
if (infVector == nullptr) {
for (int c = starter; c < contextWidth; c++) {
if (lockedWords[c] == 1) continue;
T *syn0word = syn0 + (context[c] * vectorLength);
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) {
syn0word[i] += neu1e[i];
}
}
} else {
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) {
infVector[i] += neu1e[i];
}
}
alpha = ((alpha - static_cast<double>(minLearningRate)) / static_cast<double>((iterations - i))) + static_cast<double>(minLearningRate);
}
}
BUILD_SINGLE_TEMPLATE( void cbow_,
(NDArray &syn0, NDArray &syn1,NDArray &syn1Neg, NDArray &expTable, NDArray &vnegTable, NDArray &vinfVector,
int target, int ngStarter, int *context, int *lockedWords, int *indices, int *codes,
double alpha, sd::LongType randomValue, const int contextWidth, const int hsRounds,
const int nsRounds, const int vocabSize, const int vectorLength, const int expLength,
const int negLength, const int numLabels, const bool trainWords,double minLearningRate,const int iterations),
SD_NATIVE_FLOAT_TYPES);
template <typename T>
void skipgram_(void *vsyn0, void *vsyn1, void *vsyn1Neg, void *vexpTable, void *vnegTable, void *vinfVector, int target,
int ngStarter, NDArray &indices, NDArray &codes, double alpha, sd::LongType randomValue, const int hsRounds,
const int nsRounds, const int vocabSize, const int vectorLength, const int expLength,
const int negLength,double minLearningRate,const int iterations) {
auto syn0 = reinterpret_cast<T *>(vsyn0);
auto syn1 = reinterpret_cast<T *>(vsyn1);
auto syn1Neg = reinterpret_cast<T *>(vsyn1Neg);
auto expTable = reinterpret_cast<T *>(vexpTable);
auto negTable = reinterpret_cast<T *>(vnegTable);
auto infVector = reinterpret_cast<T *>(vinfVector);
auto neu1e = new T[vectorLength];
memset(neu1e, 0, vectorLength * sizeof(T));
PRAGMA_OMP_SIMD
for(int i = 0; i < iterations; i++) {
// hierarchic softmax goes first (if enabled)
auto syn0row = infVector != nullptr ? infVector : syn0 + (target * vectorLength);
alpha = ((alpha - minLearningRate) / (iterations - i)) + minLearningRate;
auto irow = 0;
if (hsRounds > 0) {
for (int r = 0; r < hsRounds; r++) {
irow = indices.e<int>(r);
hSoftmax_<T>(syn0row, syn1 + (irow * vectorLength), expTable, neu1e, alpha, vectorLength, codes.e<int>(r), expLength,
infVector != nullptr);
}
}
// negative sampling goes second (if enabled)
auto nsStarter = ngStarter;
irow = nsStarter;
if (nsRounds > 0) {
for (int r = 0; r < nsRounds + 1; r++) {
if (r == 0) {
// target is known in advance
} else {
randomValue = randomValue * (unsigned long long)25214903917 + 11;
auto idx = sd::math::sd_abs<sd::LongType,sd::LongType>((randomValue >> 16) % negLength);
irow = idx >= negLength ? -1 : static_cast<int>(negTable[idx]);
if (irow < 0 || irow >= vocabSize) irow = randomValue % (vocabSize - 1) + 1;
if (irow == nsStarter) continue;
}
nSampling_<T>(syn0row, syn1Neg + (irow * vectorLength), expTable, neu1e, alpha, vectorLength, r == 0 ? 1 : 0,
expLength, infVector != nullptr);
}
}
if (infVector == nullptr) {
for (int e = 0; e < vectorLength; e++) {
syn0row[e] += neu1e[e];
}
} else {
for (int e = 0; e < vectorLength; e++) {
infVector[e] += neu1e[e];
}
}
alpha = ((alpha - static_cast<double>(minLearningRate)) / static_cast<double>((iterations - i))) + static_cast<double>(minLearningRate);
}
delete[] neu1e;
}
BUILD_SINGLE_TEMPLATE( void skipgram_,
(void *syn0, void *syn1, void *syn1Neg, void *expTable, void *vnegTable, void *vinfVector,
int target, int ngStarter,NDArray &indices, NDArray &codes, double alpha, sd::LongType randomValue,
const int hsRounds, const int nsRounds, const int vocabSize, const int vectorLength,
const int expLength, const int negLength,double minLearningRate,const int iterations),
SD_NATIVE_FLOAT_TYPES);
int binarySearch(const int *haystack, const int needle, const int totalElements) {
int firstIndex = 0;
int lastIndex = totalElements - 1;
int halfIndex = sd::math::sd_floor<float, int>((lastIndex + firstIndex) / (float)2);
while (haystack[halfIndex] != needle && firstIndex < lastIndex) {
if (needle < haystack[halfIndex]) {
lastIndex = halfIndex - 1;
} else if (needle > haystack[halfIndex]) {
firstIndex = halfIndex + 1;
}
halfIndex = sd::math::sd_floor<float, int>((lastIndex + firstIndex) / (float)2);
}
return (haystack[halfIndex] == needle) ? halfIndex : -1;
}
template <typename T>
void doSkipGramLoop_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray &vinfVector, NDArray&targets,
NDArray&negStarters, NDArray&indices, NDArray&codes, NDArray&lr,
NDArray&nextRandom, const int nsRounds, const int vocabSize, const int vectorLength,
const int expLength, const int negLength, T *const expTable, const T *negTable,
const LongType hsRounds, int t);
template <typename T>
void doSkipGramInferenceLoop_(NDArray &s1, NDArray &s1n, T *syn0row, NDArray&targets,
NDArray&negStarters, NDArray&indices, NDArray&codes,
const double lr, NDArray&nextRandom, const int nsRounds, const int vocabSize,
const int vectorLength, const int expLength, const int negLength, T *const expTable,
const T *negTable, const LongType hsRounds, int t, T *neu1e);
//used for lifecycle tracking in thread locals for error accumulation
template <typename T>
class BufferHolder {
public:
BufferHolder(const int vectorLength) {
neu1e = new T[vectorLength];
}
T *neu1e;
~BufferHolder() {
delete[] neu1e;
}
};
#include <cstdlib>
template <typename T, std::size_t Alignment>
class AlignedAllocator
{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template <typename U>
struct rebind { typedef AlignedAllocator<U, Alignment> other; };
AlignedAllocator() {}
template <typename U>
AlignedAllocator(const AlignedAllocator<U, Alignment>&) {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
pointer allocate(size_type n, const void* = nullptr)
{
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__)
void* ptr = this->_aligned_malloc(n * sizeof(T), Alignment);
#else
void* ptr = nullptr;
if(posix_memalign(&ptr, Alignment, n * sizeof(T)) != 0)
ptr = nullptr;
#endif
if (!ptr)
THROW_EXCEPTION("Memory allocation failed (std::bad_alloc)");
return static_cast<pointer>(ptr);
}
void deallocate(pointer p, size_type)
{
#if defined(_MSC_VER)
_aligned_free(p);
#else
std::free(p);
#endif
}
size_type max_size() const
{
return static_cast<size_type>(-1) / sizeof(T);
}
void construct(pointer p, const value_type& x)
{
::new(p) value_type(x);
}
void destroy(pointer p)
{
p->~value_type();
}
};
template <typename T>
void skipgramBatchExec_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray &vexpTable,NDArray &vnegTable, NDArray &vinfVector,
NDArray &targets, NDArray &negStarters, NDArray &indices, NDArray &codes, NDArray &lr,
NDArray &nextRandom, const int nsRounds, const int vocabSize, const int vectorLength,
const int expLength, const int negLength, const bool preciseMode, const int numThreads,const int iterations,double minLearningRate) {
const auto expTable = reinterpret_cast<T *>(vexpTable.buffer());
const auto negTable = reinterpret_cast<T *>(vnegTable.buffer());
const auto hsRounds = codes.isEmpty() ? 0 : codes.sizeAt(1);
//training
if(vinfVector.isEmpty()) {
const sd::LongType targetsLen = targets.lengthOf();
auto func = PRAGMA_THREADS_FOR {
for (auto t = start; t < stop; t+= increment) {
doSkipGramLoop_(s0, s1, s1n, vinfVector, targets, negStarters, indices, codes, lr, nextRandom, nsRounds,
vocabSize, vectorLength, expLength, negLength, expTable, negTable, hsRounds, t);
}
};
int chunkSize = 1024;
if(targetsLen < chunkSize) {
samediff::Threads::parallel_tad(func,0,targetsLen,1);
} else {
int chunks = targetsLen / chunkSize;
for(int i = 0; i < chunks; i++) {
int start = i * chunkSize;
int potentialEnd = start + chunkSize;
int end = sd::math::sd_min<int>(targetsLen,potentialEnd);
samediff::Threads::parallel_tad(func,start,end,1);
}
}
} else { //inference
auto numTargets = targets.lengthOf();
auto vec = reinterpret_cast<T *>(vinfVector.buffer());
T **neu1e = new T*[numTargets];
for(int i = 0; i < numTargets; i++) {
neu1e[i] = new T[vectorLength];
}
for(int curr = 0; curr < iterations; curr++) {
std::vector<double> lrs(numTargets);
for(int t = 0; t < numTargets; t++) {
lrs[t] = ((lr.e<double>(t) - static_cast<double>(minLearningRate)) / (static_cast<double>(iterations - curr))) + static_cast<double>(minLearningRate);
}
#pragma omp parallel for num_threads(numThreads) schedule(guided)
for(int t = 0; t < numTargets; t++) {
auto currNeu1e = neu1e[t];
std::fill_n(currNeu1e, vectorLength, T(0));
double currentLr = lrs[t];
doSkipGramInferenceLoop_(s1,s1n,vec,
targets,negStarters,
indices,
codes,
currentLr,
nextRandom,
nsRounds,
vocabSize,
vectorLength,
expLength,
negLength,
expTable,
negTable,
hsRounds,
t,
currNeu1e);
}
std::vector<std::vector<T> > buffer(numThreads, std::vector<T>(vectorLength));
#pragma omp parallel num_threads(numThreads)
{
int threadId = omp_get_thread_num();
auto& vec_local = buffer[threadId];
#pragma omp for schedule(dynamic)
for(int i = 0; i < numTargets; i++) {
for(int j = 0; j < vectorLength; j++) {
vec_local[j] += neu1e[i][j];
}
}
}
for(int j = 0; j < vectorLength; j++) {
for(const auto& vec_local : buffer) {
vec[j] += vec_local[j];
}
}
}
for(int i = 0; i < numTargets; i++) {
delete[] neu1e[i];
}
delete[] neu1e;
}// end else
}
template <typename T>
void doSkipGramInferenceLoop_(NDArray &s1, NDArray &s1n, T *syn0row, NDArray&targets,
NDArray&negStarters, NDArray&indices, NDArray&codes,
const double alpha, NDArray&nextRandom, const int nsRounds, const int vocabSize,
const int vectorLength, const int expLength, const int negLength, T *const expTable,
const T *negTable, const LongType hsRounds, int t, T *neu1e) {
if(t >= targets.lengthOf()) {
std::string errorMessage;
errorMessage += "Target index is greater than number of targets ";
errorMessage += std::to_string(t);
errorMessage += " >= ";
errorMessage += std::to_string(targets.lengthOf());
THROW_EXCEPTION(errorMessage.c_str())
}
if(t >= nextRandom.sizeAt(0)) {
std::string errorMessage;
errorMessage += "Target index is greater than number of randoms ";
errorMessage += std::to_string(t);
errorMessage += " >= ";
errorMessage += std::to_string(nextRandom.sizeAt(0));
THROW_EXCEPTION(errorMessage.c_str())
}
LongType randomValue = nextRandom.e<LongType>(t);
auto target = targets.e<int>(t);
std::vector<int> currRows(hsRounds);
std::vector<int> codes_vals(hsRounds);
#pragma omp parallel
{
#pragma omp for nowait
for (LongType e = 0; e < hsRounds; e++) {
currRows[e] = indices.e<int>(t,e);
codes_vals[e] = codes.e<int>(t,e);
}
if(nsRounds > 0) {
std::vector<int> irows(nsRounds+1, negStarters.e<int>(t));
#pragma omp for nowait
for (int r = 1; r < nsRounds + 1; r++) {
randomValue = randomValue * (unsigned long long)25214903917 + 11;
auto idx = math::sd_abs<LongType,LongType>((randomValue >> 16) % negLength);
irows[r] = idx >= negLength ? -1 : static_cast<int>(negTable[idx]);
if (irows[r] < 0 || irows[r] >= vocabSize) irows[r] = randomValue % (vocabSize - 1) + 1;
}
int nsStarter = irows[0];
#pragma omp parallel for
for (int r = 0; r < nsRounds + 1; r++) {
if (r != 0 && irows[r] == nsStarter) continue;
nSampling_<T>(syn0row, s1n.bufferWithOffset(irows[r] * vectorLength), expTable, neu1e, alpha, vectorLength,
r == 0 ? 1 : 0, expLength, true);
}
}
}
#pragma omp parallel for
for (LongType e = 0; e < hsRounds; e++) {
if(codes_vals[e] < 0) {
continue;
}
T *syn1row = (T *) s1.bufferWithOffset(currRows[e] * vectorLength);
hSoftmax_<T>(syn0row,syn1row,expTable,neu1e,alpha,vectorLength,codes_vals[e],expLength,true);
}
}
template <typename T>
void doSkipGramLoop_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray &vinfVector, NDArray&targets,
NDArray&negStarters, NDArray&indices, NDArray&codes, NDArray&lr,
NDArray&nextRandom, const int nsRounds, const int vocabSize, const int vectorLength,
const int expLength, const int negLength, T *const expTable, const T *negTable,
const LongType hsRounds, int t) {
if(t >= lr.lengthOf()) {
std::string errorMessage;
errorMessage += "Target index is greater than number of learning rates ";
errorMessage += std::to_string(t);
errorMessage += " >= ";
errorMessage += std::to_string(lr.lengthOf());
THROW_EXCEPTION(errorMessage.c_str());
}
if(t >= targets.lengthOf()) {
std::string errorMessage;
errorMessage += "Target index is greater than number of targets ";
errorMessage += std::to_string(t);
errorMessage += " >= ";
errorMessage += std::to_string(targets.lengthOf());
THROW_EXCEPTION(errorMessage.c_str())
}
T *neu1e = new T[vectorLength];
memset(neu1e, 0, vectorLength * sizeof(T));
auto alpha = lr.e<double>(t);
LongType randomValue = nextRandom.e<LongType>(t);
auto target = targets.e<int>(t);
auto syn0row = vinfVector.isEmpty() ? reinterpret_cast<T *>(s0.bufferWithOffset(target * vectorLength)) : reinterpret_cast<T *>(vinfVector.buffer());
if(hsRounds > 0) {
for (LongType e = 0; e < hsRounds; e++) {
int currRow = indices.e<int>(t,e);
int code = codes.e<int>(t,e);
//codes are only 0 and 1, -1 are placeholders for invalid codes
//the codes matrix is padded with extra values at time of allocation
//this is due to the code rows effectively being a ragged matrix (rows have different shapes)
if(code < 0) {
continue;
}
T *syn1row = (T *) s1.bufferWithOffset(currRow * vectorLength);
hSoftmax_<T>(syn0row,syn1row,expTable,neu1e,lr.e<double>(t),vectorLength,code,expLength,!vinfVector.isEmpty());
}
}
if(nsRounds > 0) {
int irow = negStarters.e<int>(t);
int nsStarter = irow;
for (int r = 0; r < nsRounds + 1; r++) {
if (r == 0) {
// target is known in advance
} else {
randomValue = randomValue * (unsigned long long)25214903917 + 11;
auto idx = math::sd_abs<LongType,LongType>((randomValue >> 16) % negLength);
irow = idx >= negLength ? -1 : static_cast<int>(negTable[idx]);
if (irow < 0 || irow >= vocabSize) irow = randomValue % (vocabSize - 1) + 1;
if (irow == nsStarter) continue;
}
nSampling_<T>(syn0row, s1n.bufferWithOffset(irow * vectorLength), expTable, neu1e, alpha, vectorLength,
r == 0 ? 1 : 0, expLength, !vinfVector.isEmpty());
}
}
PRAGMA_OMP_SIMD
for (int e = 0; e < vectorLength; e++) {
syn0row[e] += neu1e[e];
}
delete[] neu1e;
}
BUILD_SINGLE_TEMPLATE( void skipgramBatchExec_,
(NDArray & s0, NDArray &s1, NDArray &s1n, NDArray &vexpTable, NDArray &vnegTable, NDArray &vinfVector,
NDArray &targets, NDArray &negStarters, NDArray &indices, NDArray &codes, NDArray &lr,
NDArray &nextRandom, const int nsRounds, const int vocabSize, const int vectorLength,
const int expLength, const int negLength, const bool preciseMode, const int numThreads,const int iterations,double minLearningRate),
SD_NATIVE_FLOAT_TYPES);
template <typename T>
void doCbowLoop_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray&negStarters, NDArray&indices,
NDArray&codes, NDArray&lr, NDArray&nextRandom, NDArray&nLabels,
const int nsRounds, const int vocabSize, const int vectorLength, const int expLength,
const int negLength, const bool trainWords, T *const expTable, const T *negTable, const T *infVector,
const int contextWidth, const int *bContext, const int *bLocker, const int *bStarters,
const LongType numIndices, int t);
template <typename T>
void cbowBatchExec_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray &vexpTable, NDArray &vnegTable, NDArray &vinfVector,
NDArray &context, NDArray &lockedWords, NDArray &targets, NDArray &negStarters, NDArray &indices,
NDArray &codes, NDArray &lr, NDArray &nextRandom, NDArray &nLabels, const int nsRounds,
const int vocabSize, const int vectorLength, const int expLength, const int negLength,
const bool trainWords, const int numThreads,double minLearningRate,int iterations) {
const auto syn1Neg = s1n.bufferAsT<T>();
const auto expTable = vexpTable.bufferAsT<T>();
const auto negTable = vnegTable.bufferAsT<T>();
const auto infVector = vinfVector.bufferAsT<T>();
const auto idxShift = indices.isEmpty() ? 0 : indices.sizeAt(1);
const auto hsRounds = codes.isEmpty() ? 0 : codes.sizeAt(1);
const auto numTargets = context.sizeAt(0);
const int contextWidth = context.sizeAt(1);
const auto bContext = context.bufferAsT<int>();
const auto bLocker = lockedWords.bufferAsT<int>();
const auto bStarters = negStarters.bufferAsT<int>();
const auto numIndices = indices.isEmpty() ? 0 : indices.sizeAt(1);
if(vinfVector.isEmpty()) {
auto func = PRAGMA_THREADS_FOR {
for (auto t = start; t < stop; t+= increment) {
doCbowLoop_(s0, s1, s1n, negStarters, indices, codes, lr, nextRandom, nLabels, nsRounds, vocabSize,
vectorLength, expLength, negLength, trainWords, expTable, negTable, infVector, contextWidth,
bContext, bLocker, bStarters, numIndices, t);
}
};
int targetsLen = targets.lengthOf();
int chunkSize = 1024;
if(targetsLen < chunkSize) {
samediff::Threads::parallel_tad(func,0,targetsLen,1);
} else {
int chunks = targetsLen / chunkSize;
for(int i = 0; i < chunks; i++) {
int start = i * chunkSize;
int potentialEnd = start + chunkSize;
int end = sd::math::sd_min<int>(targetsLen,potentialEnd);
samediff::Threads::parallel_tad(func,start,end,1);
}
}
} else {
// regular mode provides 0 guarantees for reproducibility
auto numTargets = targets.lengthOf();
for(int iteration = 0; iteration < iterations; iteration++) {
for (auto t = 0; t < numTargets; t++) {
doCbowLoop_(s0, s1, s1n, negStarters, indices, codes, lr, nextRandom, nLabels, nsRounds, vocabSize,
vectorLength, expLength, negLength, trainWords, expTable, negTable, infVector, contextWidth,
bContext, bLocker, bStarters, numIndices, t);
}
}
}
}
template <typename T>
void doCbowLoop_(NDArray &s0, NDArray &s1, NDArray &s1n, NDArray&negStarters, NDArray&indices,
NDArray&codes, NDArray&lr, NDArray&nextRandom, NDArray&nLabels,
const int nsRounds, const int vocabSize, const int vectorLength, const int expLength,
const int negLength, const bool trainWords, T *const expTable, const T *negTable, const T *infVector,
const int contextWidth, const int *bContext, const int *bLocker, const int *bStarters,
const LongType numIndices, int t) {
T *neu1 = new T[vectorLength];
T *neu1e = new T[vectorLength];
// optionally we nullify temp arrays after successful (and on first) cycle
memset(neu1, 0, sizeof(T) * vectorLength);
memset(neu1e, 0, sizeof(T) * vectorLength);
auto alpha = lr.e<double>(t);
auto numLabels = nLabels.isEmpty() ? 0 : nLabels.e<int>(t);
int actualContext = 0;
// building neu1 for current window
for (int c = 0; c < contextWidth; c++) {
// getting next context word
auto cContext = bContext[c + (t * contextWidth)];
// skipping padded values
if (cContext < 0) continue;
if (cContext >= vocabSize) THROW_EXCEPTION("ContextID can't be >= vocab size");
T *syn0word = (T *) s0.bufferWithOffset(cContext * vectorLength);
for (int i = 0; i < vectorLength; i++) neu1[i] += syn0word[i];
actualContext++;
}
if (infVector != nullptr) actualContext++;
if (actualContext > 1) {
for (int i = 0; i < vectorLength; i++) neu1[i] /= actualContext;
}
// hierarchic softmax step
if (!indices.isEmpty()) {
for (LongType i = 0; i < numIndices; i++) {
const int cIndex = indices.e<int>(t,i);
const int cCode = codes.e<int>(t,i);
// we're skipping padded values
if (cIndex < 0) continue;
if (cIndex >= vocabSize) THROW_EXCEPTION("Index can't be > vocab size");
hSoftmax_<T>(neu1, s1.bufferasTWithOffset<T>(cIndex * vectorLength), expTable, neu1e, alpha, vectorLength, cCode, expLength,
false);
}
}
// negative sampling step
if (!negStarters.isEmpty() && nsRounds > 0) {
int irow = bStarters[t];
const int nsStarter = irow;
unsigned long long randomValue = nextRandom.e<LongType>(t);
for (int r = 0; r < nsRounds + 1; r++) {
// we're skipping rng on 0 step
if (r != 0) {
randomValue = randomValue * (unsigned long long)25214903917 + 11;
auto idx = math::sd_abs<LongType,LongType>((randomValue >> 16) % negLength);
irow = idx >= negLength ? -1 : static_cast<int>(negTable[idx]);
if (irow < 0 || irow >= vocabSize) irow = randomValue % (vocabSize - 1) + 1;
if (irow == nsStarter) continue;
nSampling_<T>(neu1, s1n.bufferWithOffset(irow * vectorLength), expTable, neu1e, alpha, vectorLength,
r == 0 ? 1 : 0, expLength, infVector != nullptr);
} else {
nSampling_<T>(neu1, s1n.bufferWithOffset(irow * vectorLength), expTable, neu1e, alpha, vectorLength,
r == 0 ? 1 : 0, expLength, infVector != nullptr);
}
}
}
// if we're skipping labels
int starter = trainWords == 1 ? 0 : contextWidth - numLabels;
// applying previously averaged results
for (int c = starter; c < contextWidth; c++) {
// getting context
auto cContext = bContext[c + (t * contextWidth)];
auto cLock = bLocker[c + (t * contextWidth)];
// skipping padded values
if (cContext < 0 || cLock == 1) continue;
if (cContext >= vocabSize) THROW_EXCEPTION("ContextID can't be > vocab size");
// one word from context
T *syn0word = (T *) s0.bufferWithOffset(cContext * vectorLength);
PRAGMA_OMP_SIMD
for (int i = 0; i < vectorLength; i++) syn0word[i] += neu1e[i];
}
// optionally release temp arrays
if (vectorLength > 600) {
delete[] neu1;
delete[] neu1e;
}
}
BUILD_SINGLE_TEMPLATE( void cbowBatchExec_,
(NDArray & s0, NDArray &s1, NDArray &s1n, NDArray &vexpTable, NDArray &vnegTable, NDArray &vinfVector,
NDArray &context, NDArray &lockedWords, NDArray &targets, NDArray &negStarters, NDArray &indices,
NDArray &codes, NDArray &lr, NDArray &nextRandom, NDArray &nLabels, const int nsRounds,
const int vocabSize, const int vectorLength, const int expLength, const int negLength,
const bool trainWords, const int numThreads,double minLearningRate,const int iterations),
SD_NATIVE_FLOAT_TYPES);
void skipgramInference(NDArray &syn0, NDArray &syn1, NDArray &syn1Neg, NDArray &expTable, NDArray &negTable, int target,
int ngStarter, int nsRounds, NDArray &indices, NDArray &codes, double alpha, sd::LongType randomValue,
NDArray &inferenceVector, const bool preciseMode, const int numWorkers,double minLearningRate,const int iterations) {
auto xType = syn0.dataType();
auto hsRounds = codes.lengthOf();
BUILD_SINGLE_SELECTOR(
xType, skipgram_,
(syn0.buffer(), syn1.buffer(), syn1Neg.buffer(), expTable.buffer(), negTable.buffer(), inferenceVector.buffer(),
target, ngStarter,
indices, codes, alpha,
randomValue, hsRounds, nsRounds, (int)syn0.sizeAt(0), (int)syn0.sizeAt(1),
(int)expTable.lengthOf(), (int)negTable.lengthOf(),minLearningRate,iterations),
SD_NATIVE_FLOAT_TYPES);
}
void cbowInference(NDArray &syn0, NDArray &syn1, NDArray &syn1Neg, NDArray &expTable, NDArray &negTable, int target,
int ngStarter, int nsRounds, NDArray &context, NDArray &lockedWords, NDArray &indices, NDArray &codes,
double alpha, sd::LongType randomValue, int numLabels, NDArray &inferenceVector, const bool trainWords,
int numWorkers,int iterations,double minLearningRate) {
auto xType = syn0.dataType();
auto hsRounds = codes.lengthOf();
BUILD_SINGLE_SELECTOR(
xType, cbow_,
(syn0, syn1, syn1Neg, expTable, negTable, inferenceVector,
target, ngStarter,
context.bufferAsT<int>(), lockedWords.bufferAsT<int>(),
indices.bufferAsT<int>(), codes.bufferAsT<int>(), alpha,
randomValue, (int)context.lengthOf(), hsRounds, nsRounds, (int)syn0.sizeAt(0),
(int)syn0.sizeAt(1), (int)expTable.lengthOf(), (int)negTable.lengthOf(),
numLabels, trainWords,minLearningRate,iterations),
SD_NATIVE_FLOAT_TYPES);
}
void skipgram(NDArray &syn0, NDArray &syn1, NDArray &syn1Neg, NDArray &expTable, NDArray &negTable, NDArray &target,
NDArray &ngStarter, int nsRounds, NDArray &indices, NDArray &codes, NDArray &alpha, NDArray &randomValue,
NDArray &inferenceVector, const bool preciseMode, const int numWorkers,const int iterations,double minLearningRate) {
auto xType = syn0.dataType();
// single round case
if ((ngStarter.isScalar() && !ngStarter.isEmpty()) || (target.isScalar() && !target.isEmpty())) {
auto hsRounds = codes.lengthOf();
BUILD_SINGLE_SELECTOR(
xType, skipgram_,
(syn0.buffer(), syn1.buffer(), syn1Neg.buffer(), expTable.buffer(), negTable.buffer(), inferenceVector.buffer(),
target.isEmpty() ? -1 : target.e<int>(0), ngStarter.isEmpty() ? -1 : ngStarter.e<int>(0),
indices, codes, alpha.e<double>(0),
randomValue.e<sd::LongType>(0), hsRounds, nsRounds, (int)syn0.sizeAt(0), (int)syn0.sizeAt(1),
(int)expTable.lengthOf(), (int)negTable.lengthOf(),minLearningRate,iterations),
SD_NATIVE_FLOAT_TYPES);
} else if (ngStarter.isVector() || target.isVector()) {
// batch mode
BUILD_SINGLE_SELECTOR(xType, skipgramBatchExec_,
(syn0, syn1, syn1Neg, expTable, negTable, inferenceVector, target, ngStarter,
indices, codes, alpha, randomValue, nsRounds, syn0.sizeAt(0), syn0.sizeAt(1),
expTable.lengthOf(), negTable.lengthOf(), preciseMode, numWorkers,iterations,minLearningRate),
SD_NATIVE_FLOAT_TYPES);
} else
THROW_EXCEPTION("SkipGram: target must have rank 0 or 1");
}
void cbow(NDArray &syn0, NDArray &syn1, NDArray &syn1Neg, NDArray &expTable, NDArray &negTable, NDArray &target,
NDArray &ngStarter, int nsRounds, NDArray &context, NDArray &lockedWords, NDArray &indices, NDArray &codes,
NDArray &alpha, NDArray &randomValue, NDArray &numLabels, NDArray &inferenceVector, const bool trainWords,
int numWorkers,double minLearningRate,const int iterations) {
auto xType = syn0.dataType();
if ((context.rankOf() == 0 || context.rankOf() == 1) && (indices.rankOf() == 1 || indices.rankOf() == 0)) {
auto hsRounds = codes.lengthOf();
//convert every inline parameter below in to a variable
BUILD_SINGLE_SELECTOR(
xType, cbow_,
(syn0,
syn1,
syn1Neg,
expTable,
negTable,
inferenceVector,
target.isEmpty() ? -1 : target.e<int>(0),
ngStarter.isEmpty() ? -1 : ngStarter.e<int>(0),
context.isEmpty() ? nullptr : context.bufferAsT<int>(),
lockedWords.isEmpty() ? nullptr : lockedWords.bufferAsT<int>(),
indices.isEmpty() ? nullptr : indices.bufferAsT<int>(),
codes.isEmpty() ? nullptr : codes.bufferAsT<int>(),
alpha.isEmpty() ? 0.025 : alpha.e<double>(0),
randomValue.isEmpty() ? -1 : randomValue.e<sd::LongType>(0),
(int)context.lengthOf(),
hsRounds,
nsRounds,
(int)syn0.sizeAt(0),
syn1.isEmpty() ? 0 : (int)syn0.sizeAt(1),
expTable.isEmpty() ? 0 : (int)expTable.lengthOf(),
negTable.isEmpty() ? 0 : (int)negTable.lengthOf(),
numLabels.isEmpty() ? 0 : numLabels.e<int>(0),
trainWords,minLearningRate,iterations),
SD_NATIVE_FLOAT_TYPES);
} else if (context.rankOf() == 2 && indices.rankOf() == 2) {
BUILD_SINGLE_SELECTOR(
xType, cbowBatchExec_,
(syn0, syn1, syn1Neg, expTable, negTable, inferenceVector, context, lockedWords, target, ngStarter,
indices, codes, alpha, randomValue, numLabels, nsRounds, syn0.sizeAt(0), syn0.sizeAt(1), expTable.lengthOf(),
negTable.isEmpty() ? 0 : negTable.lengthOf(), trainWords, numWorkers,minLearningRate,iterations),
SD_NATIVE_FLOAT_TYPES);
} else
THROW_EXCEPTION("CBOW: context must have rank 0/1 or 2");
}
} // namespace helpers
} // namespace ops
} // namespace sd