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

1362 lines
55 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ******************************************************************************
*
*
* 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)
//
// implementation of operation for LSTM cell with peep hole connections:
// http://www.bioinf.jku.at/publications/older/2604.pdf
// S. Hochreiter and J. Schmidhuber. "Long Short-Term Memory". Neural Computation, 9(8):1735-1780, 1997.
// and
// https://research.google.com/pubs/archive/43905.pdf
// Hasim Sak, Andrew Senior, and Francoise Beaufays. "Long short-term memory recurrent neural network architectures for
// large scale acoustic modeling." INTERSPEECH, 2014.
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_lstmLayer)
#include <execution/Threads.h>
#include <helpers/MmulHelper.h>
#include <helpers/ShapeUtils.h>
#include <ops/declarable/helpers/activations.h>
#include <ops/declarable/helpers/lstmLayer.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
static void applyActivation(NDArray *x, const int opId, const float alpha, const float beta, NDArray* z) {
switch (opId) {
case 0:
x->applyTransform(transform::Tanh, z);
break;
case 1:
x->applyScalar(scalar::RELU, 0, z);
break;
case 2:
x->applyTransform(transform::Sigmoid, z);
break;
case 3: {
ExtraArguments args({static_cast<double>(alpha), static_cast<double>(beta)});
x->applyTransform(transform::Affine, z, &args);
break;
}
case 4:
x->applyScalar(scalar::LeakyRELU, alpha, z);
break;
case 5:
thresholdRelu(x->getContext(), x, alpha, z);
break;
case 6: {
ExtraArguments args({static_cast<double>(alpha), static_cast<double>(beta)});
x->applyTransform(transform::ScaledTanh, z, &args);
break;
}
case 7:
x->applyTransform(transform::HardSigmoid, z);
break;
case 8:
x->applyScalar(scalar::ELU, alpha, z);
break;
case 9:
x->applyTransform(transform::SoftSign, z);
break;
case 10:
x->applyTransform(transform::SoftPlus, z);
break;
default:
THROW_EXCEPTION("LSTM_LAYER operation: wrong id number of activation !");
}
}
//////////////////////////////////////////////////////////////////////////
static void activationDeriv(NDArray* x, const int opId, const float alpha, const float beta, NDArray* z) {
switch (opId) {
case 0:
x->applyTransform(transform::TanhDerivative, z);
break;
case 1:
x->applyScalar(scalar::RELUDerivative, 0, z);
break;
case 2:
x->applyTransform(transform::SigmoidDerivative, z);
break;
case 3: {
*z = alpha;
break;
}
case 4:
x->applyScalar(scalar::LeakyRELUDerivative, alpha, z);
break;
case 5:
thresholdReluDerivative(x->getContext(), x, alpha, z, z);
break;
case 6: {
auto func = PRAGMA_THREADS_FOR {
for (sd::LongType i = start; i < stop; ++i) {
auto val = beta * x->e<float>(i);
z->p<float>(
i, alpha * beta * (1.f - sd::math::sd_tanh<float, float>(val) * sd::math::sd_tanh<float, float>(val)));
}
};
samediff::Threads::parallel_for(func, 0, x->lengthOf());
break;
}
case 7:
x->applyTransform(transform::HardSigmoidDerivative, z);
break;
case 8:
x->applyScalar(scalar::ELUDerivative, alpha, z);
break;
case 9:
x->applyTransform(transform::SoftSignDerivative, z);
break;
case 10: {
auto func = PRAGMA_THREADS_FOR {
for (sd::LongType i = start; i < stop; ++i) {
auto val = sd::math::sd_exp<float, float>(x->e<float>(i));
z->p<float>(i, val / (1.f + val));
}
};
samediff::Threads::parallel_for(func, 0, x->lengthOf());
break;
}
default:
THROW_EXCEPTION("LSTM_LAYER operation: wrong id number of activation !");
}
}
//////////////////////////////////////////////////////////////////////////
// FIXME - derivative undefined when not-clipped c has element/elements equal to -clipVal or clipVal
static void clipDeriv(const float clipVal, NDArray& c, NDArray& z0, NDArray& z1, NDArray& z2, NDArray& z3) {
if (clipVal == 0) return;
auto func = PRAGMA_THREADS_FOR {
for (sd::LongType i = start; i < stop; ++i) {
const auto val = c.e<float>(i);
if (val == -clipVal || val == clipVal) {
z0.p<float>(i, 0.f);
z1.p<float>(i, 0.f);
z2.p<float>(i, 0.f);
z3.p<float>(i, 0.f);
}
}
};
samediff::Threads::parallel_for(func, 0, c.lengthOf());
}
//////////////////////////////////////////////////////////////////////////
static NDArray *tensorAlongTimeBatchDims(NDArray& arr, const int dataFormat, const int t1, const int t2,
const int b1, const int b2) {
if (dataFormat == 0 || dataFormat == 3) return arr({t1, t2, b1, b2, 0, 0}); // TNS: [sL, bS, nIn]
if (dataFormat == 1) return arr({b1, b2, t1, t2, 0, 0}); // NTS: [bS, sL ,nIn]
return arr({b1, b2, 0, 0, t1, t2}); // NST: [bS, nIn, sL]
}
//////////////////////////////////////////////////////////////////////////
static SD_INLINE int getBatchTimeTotalIndex(const int dataFormat, const int sL, const int bS, const int t,
const int b) {
if (dataFormat == 0 || dataFormat == 3) return t * bS + b; // TNS: shape [sL, bS, nIn]
return b * sL + t; // NTS, NST: shape [bS, sL, nIn], [bS, nIn, sL]
}
//////////////////////////////////////////////////////////////////////////
void lstmLayerCell(NDArray* x, NDArray* Wx, NDArray* Wr, NDArray* b, NDArray* hI,
NDArray* cI, NDArray* Wp, const std::vector<float>& params, NDArray* h, NDArray* c) {
// * -> means element-wise multiplication
// × -> means matrix multiplication
/************************ THIS IS NOT OPTIMAZED CODE ***********************************/
/** the objective is to provide math-readable code **/
// equations (no peephole connections)
// it = σ(Wxi × xt + Wri × ht-1 + bi)
// ft = σ(Wxf × xt + Wrf × ht-1 + bf)
// c't = tanh(Wxc × xt + Wrc × ht-1 + bc)
// ct = ft * ct-1 + it * c't
// ot = σ(Wxo × xt + Wro × ht-1 + bo)
// ht = ot * tanh(ct)
// equations (peephole connections are present)
// it = σ(Wxi × xt + Wri × ht-1 + Wpi * ct-1 + bi)
// ft = σ(Wxf × xt + Wrf × ht-1 + Wpf * ct-1 + bf)
// c't = tanh(Wxc × xt + Wrc × ht-1 + bc)
// ct = ft * ct-1 + it * c't
// ot = σ(Wxo × xt + Wro × ht-1 + Wpo * ct + bo)
// ht = ot * tanh(ct)
// IDs for activations: 0=tanh, 1=relu, 2=sigmoid, 3=affine, 4=leaky relu, 5= thresholded relu, 6=scaled tanh, 7=hard
// sigmoid, 8=ELU, 9=softsign, 10=softplus
// params[0] - dataFormat, ignore
// params[1] - directionMode, ignore
// params[2] - cell clipping value, if it = 0 then do not apply clipping
// params[3] - activation ID for input (i), forget (f) and output (o) gates
// params[4] - alpha value for gates activation
// params[5] - beta value for gates activation
// params[6] - activation ID for cell state (c)
// params[7] - alpha value for cell state activation
// params[8] - beta value for cell state activation
// params[9] - activation ID for output (h)
// params[10] - alpha value for output activation
// params[11] - beta value for output activation
// INPUTS:
// x - current input at time t, [bS, nIn] or [nIn] if seqLen != nullptr
// Wx - input weights [nIn, 4*nOut]
// Wr - recurrent weights [nOut, 4*nOut]
// b - biases [4*nOut], optional, may be nullptr
// hI - (ht-1) previous (initial) output at time t-1, optional may be nullptr, [bS, nOut] or [nOut] if seqLen !=
// nullptr cI - (ct-1) previous (initial) cell state at time t-1, optional may be nullptr, [bS, nOut] or [nOut] if
// seqLen != nullptr Wp - peephole weights [3*nOut], optional, may be nullptr
// OUTPUTS:
// h - current output, that is at current time step t, [bS, nOut] or [nOut] if seqLen != nullptr
// c - current cell state, that is at current time step t, [bS, nOut] or [nOut] if seqLen != nullptr
// !!! dimension 4*nOut implies order it, ft, c't, ot
// !!! dimension 3*nOut implies order it, ft, ot
const sd::LongType nOut = Wx->sizeAt(-1) / 4;
NDArray *xMulWx = mmul(*x, *Wx); // [bs, nIn] * [nIn, 4*nOut] = [bS, 4*nOut] or [nIn] * [nIn, 4*nOut] = [4*nOut]
NDArray *hIMulWr = mmul(*hI, *Wr); // [bs, nOut] * [nOut, 4*nOut] = [bS, 4*nOut] or [nOut] * [nOut, 4*nOut] = [4*nOut]
auto* z = (*xMulWx) + (*hIMulWr);
delete xMulWx;
delete hIMulWr;
// add biases if they are given
if (b != nullptr) *z += *b; // broadcast [bS, 4*nOut](or[4*nOut]) + [4*nOut] = [bS, 4*nOut]
auto zi = x->rankOf() == 1 ? (*z)({0, nOut}) : (*z)({0, 0, 0, nOut}); // input gate it, [bS, nOut](or[nOut])
auto zf = x->rankOf() == 1 ? (*z)({nOut, 2 * nOut}) : (*z)({0, 0, nOut, 2 * nOut}); // forget gate ft, [bS, nOut](or[nOut])
auto zg = x->rankOf() == 1 ? (*z)({2 * nOut, 3 * nOut})
: (*z)({0, 0, 2 * nOut, 3 * nOut}); // cell gate c't, [bS, nOut](or[nOut])
auto zo = x->rankOf() == 1 ? (*z)({3 * nOut, 4 * nOut})
: (*z)({0, 0, 3 * nOut, 4 * nOut}); // output gate ot, [bS, nOut](or[nOut])
// peephole connections for input and forget gates
if (Wp != nullptr) {
NDArray *wpFirst = (*Wp)({0, nOut});
NDArray *wpSecond = (*Wp)({nOut, 2 * nOut});
NDArray *cIMulWpFirst = (*cI) * (*wpFirst); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
NDArray *cIMulWpSecond = (*cI) * (*wpSecond); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
*zi += *cIMulWpFirst;
*zf += *cIMulWpSecond;
delete cIMulWpFirst;
delete cIMulWpSecond;
delete wpFirst;
delete wpSecond;
}
applyActivation(zi, params[3], params[4], params[5], zi); // inplace
applyActivation(zf, params[3], params[4], params[5], zf); // inplace
applyActivation(zg, params[6], params[7], params[8], zg); // inplace
NDArray *zfMulCI = (*zf) * (*cI); // [bS, nOut] * [bS, nOut] = [bS, nOut](or[nOut])
NDArray *ziMulZg = (*zi) * (*zg); // [bS, nOut] * [bS, nOut] = [bS, nOut](or[nOut])
NDArray *cAssign = (*zfMulCI) + (*ziMulZg);
c->assign(cAssign);
delete zfMulCI;
delete ziMulZg;
delete cAssign;
// if clipping value is non-zero then cell state is clipped by this value prior to the cell output activation
if (params[2] != 0) c->applyScalar(scalar::LstmClip, params[2], c);
// peephole connections for output gate
if (Wp != nullptr) {
NDArray *wpThird = (*Wp)({2 * nOut, 3 * nOut});
NDArray *cMulWpThird = (*c) * (*wpThird); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
*zo += *cMulWpThird;
delete cMulWpThird;
delete wpThird;
}
applyActivation(zo, params[3], params[4], params[5], zo);
applyActivation(c, params[9], params[10], params[11], h);
*h *= *zo; // [bS, nOut] * [bS, nOut](or[nOut])
delete z;
}
//////////////////////////////////////////////////////////////////////////
// this auxiliary ff should be running before backprop
void lstmLayerCell(NDArray* x, NDArray* Wx, NDArray* Wr, NDArray* b, NDArray* hI,
NDArray* cI, NDArray* Wp, const std::vector<float>& params, NDArray* z, NDArray* a,
NDArray* h, NDArray* c) {
// z - zi, zf, zg, zo
// a - i, f, g, o
const sd::LongType nOut = Wx->sizeAt(-1) / 4;
NDArray *xMulWx = mmul(*x, *Wx); // [bs, nIn] * [nIn, 4*nOut] = [bS, 4*nOut] or [nIn] * [nIn, 4*nOut] = [4*nOut]
NDArray *hIMulWr = mmul(*hI, *Wr); // [bs, nOut] * [nOut, 4*nOut] = [bS, 4*nOut] or [nOut] * [nOut, 4*nOut] = [4*nOut]
NDArray *zAssign = (*xMulWx) + (*hIMulWr);
z->assign(zAssign);
delete xMulWx;
delete hIMulWr;
delete zAssign;
// add biases if they are given
if (b != nullptr) *z += *b; // broadcast [bS, 4*nOut](or[4*nOut]) + [4*nOut] = [bS, 4*nOut]
auto zi = x->rankOf() == 1 ? (*z)({0, nOut}) : (*z)({0, 0, 0, nOut}); // input gate it, [bS, nOut](or[nOut])
auto zf =
x->rankOf() == 1 ? (*z)({nOut, 2 * nOut}) : (*z)({0, 0, nOut, 2 * nOut}); // forget gate ft, [bS, nOut](or[nOut])
auto zg = x->rankOf() == 1 ? (*z)({2 * nOut, 3 * nOut})
: (*z)({0, 0, 2 * nOut, 3 * nOut}); // cell gate c't, [bS, nOut](or[nOut])
auto zo = x->rankOf() == 1 ? (*z)({3 * nOut, 4 * nOut})
: (*z)({0, 0, 3 * nOut, 4 * nOut}); // output gate ot, [bS, nOut](or[nOut])
auto i = x->rankOf() == 1 ? (*a)({0, nOut}) : (*a)({0, 0, 0, nOut}); // input gate it, [bS, nOut](or[nOut])
auto f =
x->rankOf() == 1 ? (*a)({nOut, 2 * nOut}) : (*a)({0, 0, nOut, 2 * nOut}); // forget gate ft, [bS, nOut](or[nOut])
auto g = x->rankOf() == 1 ? (*a)({2 * nOut, 3 * nOut})
: (*a)({0, 0, 2 * nOut, 3 * nOut}); // cell gate c't, [bS, nOut](or[nOut])
auto o = x->rankOf() == 1 ? (*a)({3 * nOut, 4 * nOut})
: (*a)({0, 0, 3 * nOut, 4 * nOut}); // output gate ot, [bS, nOut](or[nOut])
// peephole connections for input and forget gates
if (Wp != nullptr) {
NDArray *wpFirst = (*Wp)({0, nOut});
NDArray *wpSecond = (*Wp)({nOut, 2 * nOut});
NDArray *cIMulWpFirst = (*cI) * (*wpFirst); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
NDArray *cIMulWpSecond = (*cI) * (*wpSecond); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
*zi += *cIMulWpFirst;
*zf += *cIMulWpSecond;
delete cIMulWpFirst;
delete cIMulWpSecond;
delete wpFirst;
delete wpSecond;
}
applyActivation(zi, params[3], params[4], params[5], i);
applyActivation(zf, params[3], params[4], params[5], f);
applyActivation(zg, params[6], params[7], params[8], g);
NDArray *fMulCI = (*f) * (*cI); // [bS, nOut] * [bS, nOut] = [bS, nOut](or[nOut])
NDArray *iMulG = (*i) * (*g); // [bS, nOut] * [bS, nOut] = [bS, nOut](or[nOut])
NDArray *cAssign = (*fMulCI) + (*iMulG);
c->assign(cAssign);
delete fMulCI;
delete iMulG;
delete cAssign;
// if clipping value is non-zero then cell state is clipped by this value prior to the cell output activation
if (params[2] != 0) c->applyScalar(scalar::LstmClip, params[2], c);
// peephole connections for output gate
if (Wp != nullptr) {
NDArray *wpThird = (*Wp)({2 * nOut, 3 * nOut});
NDArray *cMulWpThird = (*c) * (*wpThird); // broadcast: [bS, nOut] * [nOut] = [bS, nOut](or[nOut])
*zo += *cMulWpThird;
delete cMulWpThird;
delete wpThird;
}
applyActivation(zo, params[3], params[4], params[5], o);
applyActivation(c, params[9], params[10], params[11], h);
*h *= *o; // [bS, nOut] * [bS, nOut](or[nOut])
}
//////////////////////////////////////////////////////////////////////////
void lstmLayerCellBp(NDArray* x, NDArray* Wx, NDArray* Wr, NDArray* b, NDArray* hI,
NDArray* cI, NDArray* Wp, NDArray* dLdh, NDArray* dLdhL,
NDArray* dLdcL, NDArray* z, NDArray* a, NDArray* c,
const std::vector<float>& params, NDArray* dLdx, NDArray* dLdWx, NDArray* dLdWr, NDArray* dLdhI,
NDArray* dLdcI, NDArray* dLdb, NDArray* dLdWp) {
/************************ THIS IS NOT OPTIMAZED CODE ***********************************/
/** the objective is to provide math-readable code **/
// equations (no peephole connections)
// zi = x × Wxi + hI × Wri + bi
// zf = x × Wxf + hI × Wrf + bf
// zg = x × Wxg + hI × Wrg + bg
// zo = x × Wxo + hI × Wro + bo
// i = act(zi)
// f = act(zf)
// g = actC(zg)
// o = act(zo)
// c = clip(f * cI + i * g)
// h = o * actH(c)
// equations (peephole connections are present)
// zi = x × Wxi + hI × Wri + cI * Wpi + bi
// zf = x × Wxf + hI × Wrf + cI * Wpf + bf
// zg = x × Wxg + hI × Wrg + bg
// zo = x × Wxo + hI × Wro + c * Wpo + bo
// i = act(zi)
// f = act(zf)
// g = actC(zg)
// o = act(zo)
// c = clip(f * cI + i * g)
// h = o * actH(c)
// IDs for activations: 0=tanh, 1=relu, 2=sigmoid, 3=affine, 4=leaky relu, 5= thresholded relu, 6=scaled tanh, 7=hard
// sigmoid, 8=ELU, 9=softsign, 10=softplus
// params[0] - dataFormat, ignore
// params[1] - directionMode, ignore
// params[2] - cell clipping value, if it = 0 then do not apply clipping
// params[3] - activation ID for input (i), forget (f) and output (o) gates
// params[4] - alpha value for gates activation
// params[5] - beta value for gates activation
// params[6] - activation ID for cell state (c)
// params[7] - alpha value for cell state activation
// params[8] - beta value for cell state activation
// params[9] - activation ID for output (h)
// params[10] - alpha value for output activation
// params[11] - beta value for output activation
// INPUTS:
// x - current input at time t, [bS, nIn] or [nIn] if seqLen != nullptr
// Wx - input weights [nIn, 4*nOut]
// Wr - recurrent weights [nOut, 4*nOut]
// b - biases [4*nOut], optional, may be nullptr
// hI - (ht-1) previous (initial) output at time t-1, [bS, nOut] or [nOut] if seqLen != nullptr
// cI - (ct-1) previous (initial) cell state at time t-1, [bS, nOut] or [nOut] if seqLen != nullptr
// Wp - peephole weights [3*nOut], optional, may be nullptr
// dLdh - loss derivative with respect to h at each time step, [bS, nOut] or [nOut] if seqLen != nullptr
// dLdhL - loss derivative with respect to h at last time step, [bS, nOut] or [nOut] if seqLen != nullptr
// dLdcL - loss derivative with respect to c at last time step, [bS, nOut] or [nOut] if seqLen != nullptr
// z - zi,zf,zg,zo taken from ff outputs to reduce amount of calculations in bp, [bS, 4*nOut]
// a - i,f,g,o taken from ff outputs to reduce amount of calculations in bp, [bS, 4*nOut]
// c - taken from ff outputs to reduce amount of calculations in bp, [bS, nOut]
// OUTPUTS:
// dLdx - loss derivative with respect to x, [bS, nIn] or [nIn] if seqLen != nullptr
// dLdWx - loss derivative with respect to Wx, [nIn, 4*nOut]
// dLdWr - loss derivative with respect to Wr, [nOut, 4*nOut]
// dLdb - loss derivative with respect to b, optional, may be nullptr, [4*nOut]
// dLdhI - loss derivative with respect to hI, optional may be nullptr, [bS, nOut] or [nOut] if seqLen != nullptr
// dLdcI - loss derivative with respect to cI, optional may be nullptr, [bS, nOut] or [nOut] if seqLen != nullptr
// dLdWp - loss derivative with respect to Wp, optional, may be nullptr, [3*nOut]
// !!! dimension 4*nOut implies order i, f, g, o
// !!! dimension 3*nOut implies order i, f, o
// dhdc = o*tanhDeriv + Wp ? tanh(c)*dodzo*dzodc : 0 [bS, nOut]
// dcdcI = f + Wp ? dcdzi*dzidcI + dcdzf*dzfdcI : 0 [bS, nOut]
// dLdhI += dLdh; [bS, nOut]
// dLdcI += dLdhI * dhdc; [bS, nOut]
// dLdzi = dLdcI*dcdi*didzi; [bS, nOut](or[nOut])
// dLdzf = dLdcI*dcdf*dfdzf; [bS, nOut](or[nOut])
// dLdzg = dLdcI*dcdg*dgdzg; [bS, nOut](or[nOut])
// dLdzo = dLdhI*dhdo*dodzo; [bS, nOut](or[nOut])
// dLdx = dLdzi×WxiT + dLdzf×WxfT + dLdzg×WxgT + dLdzo×WxoT, [bS, nIn]
// dLdhI = dLdzi×WriT + dLdzf×WrfT + dLdzg×WrgT + dLdzo×WroT, [bS, nOut]
// dLdcI = dLdcI*dcdcI, [bS, nOut]
// dLdWxi = xT×dLdzi [nIn, bS] x [bS, nOut] = [nIn, nOut]
// dLdWxf = xT×dLdzf [nIn, bS] x [bS, nOut] = [nIn, nOut]
// dLdWxg = xT×dLdzg [nIn, bS] x [bS, nOut] = [nIn, nOut]
// dLdWxo = xT×dLdzo [nIn, bS] x [bS, nOut] = [nIn, nOut]
// dLdWri = hIT×dLdzi [nOut, bS] x [bS, nOut] = [nOut, nOut]
// dLdWrf = hIT×dLdzf [nOut, bS] x [bS, nOut] = [nOut, nOut]
// dLdWrg = hIT×dLdzg [nOut, bS] x [bS, nOut] = [nOut, nOut]
// dLdWro = hIT×dLdzo [nOut, bS] x [bS, nOut] = [nOut, nOut]
// dLdbi = dLdzi.reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdbf = dLdzf.reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdbg = dLdzg.reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdbo = dLdzo.reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdWpi = (dLdzi*cI).reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdWpf = (dLdzf*cI).reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
// dLdWpo = (dLdzo*c) .reduce_sum_along_0_axis [bS, nOut] -> reduce -> [nOut]
const sd::LongType nOut = Wx->sizeAt(-1) / 4;
const sd::LongType nIn = x->sizeAt(-1);
NDArray *zi = x->rankOf() == 1 ? (*z)({0, nOut}) : (*z)({0, 0, 0, nOut}); // input gate i, [bS, nOut](or[nOut])
NDArray *zf =
x->rankOf() == 1 ? (*z)({nOut, 2 * nOut}) : (*z)({0, 0, nOut, 2 * nOut}); // forget gate f, [bS, nOut](or[nOut])
NDArray *zg = x->rankOf() == 1 ? (*z)({2 * nOut, 3 * nOut})
: (*z)({0, 0, 2 * nOut, 3 * nOut}); // cell gate g, [bS, nOut](or[nOut])
NDArray *zo = x->rankOf() == 1 ? (*z)({3 * nOut, 4 * nOut})
: (*z)({0, 0, 3 * nOut, 4 * nOut}); // output gate o, [bS, nOut](or[nOut])
NDArray *i = x->rankOf() == 1 ? (*a)({0, nOut}) : (*a)({0, 0, 0, nOut}); // input gate i, [bS, nOut](or[nOut])
NDArray *f =
x->rankOf() == 1 ? (*a)({nOut, 2 * nOut}) : (*a)({0, 0, nOut, 2 * nOut}); // forget gate f, [bS, nOut](or[nOut])
NDArray *g = x->rankOf() == 1 ? (*a)({2 * nOut, 3 * nOut})
: (*a)({0, 0, 2 * nOut, 3 * nOut}); // cell gate g, [bS, nOut](or[nOut])
NDArray *o = x->rankOf() == 1 ? (*a)({3 * nOut, 4 * nOut})
: (*a)({0, 0, 3 * nOut, 4 * nOut}); // output gate o, [bS, nOut](or[nOut])
NDArray *zUlike = z->ulike(); // [bS, 4*nOut](or[4*nOut])
NDArray dLdz = *zUlike; // [bS, 4*nOut](or[4*nOut])
NDArray *dLdzi = x->rankOf() == 1 ? dLdz({0, nOut}) : dLdz({0, 0, 0, nOut});
NDArray *dLdzf = x->rankOf() == 1 ? dLdz({nOut, 2 * nOut}) : dLdz({0, 0, nOut, 2 * nOut});
NDArray *dLdzg = x->rankOf() == 1 ? dLdz({2 * nOut, 3 * nOut}) : dLdz({0, 0, 2 * nOut, 3 * nOut});
NDArray *dLdzo = x->rankOf() == 1 ? dLdz({3 * nOut, 4 * nOut}) : dLdz({0, 0, 3 * nOut, 4 * nOut});
// dcdzi = dcdi*didzi, [bS, nOut](or[nOut])
activationDeriv(zi, params[3], params[4], params[5], dLdzi); // didzi, inplace
NDArray *dLdziMulG = (*dLdzi) * (*g); // dcdi = g*clipDeriv
dLdzi->assign(dLdziMulG);
delete dLdziMulG;
// dcdzf = dcdf*dfdzf, [bS, nOut](or[nOut])
activationDeriv(zf, params[3], params[4], params[5], dLdzf); // dfdzf, inplace
NDArray *dLdzfMulCI = (*dLdzf) * (*cI); // dcdf = cI*clipDeriv
dLdzf->assign(dLdzfMulCI);
delete dLdzfMulCI;
// dcdzg = dcde*dedzg, [bS, nOut](or[nOut])
activationDeriv(zg, params[6], params[7], params[8], dLdzg); // dgdzg, inplace
NDArray *dLdzgMulI = (*dLdzg) * (*i); // dcdf = i*clipDeriv
dLdzg->assign(dLdzgMulI);
delete dLdzgMulI;
// dhdzo = dhdo*dodzo = actH(c)*dodzo, [bS, nOut](or[nOut])
activationDeriv(zo, params[3], params[4], params[5], dLdzo);
NDArray *dLdzoUlike = dLdzo->ulike();
NDArray temp = *dLdzoUlike;
applyActivation(c, params[9], params[10], params[11], &temp); // actH(c), inplace
NDArray *dLdzoMulTemp = (*dLdzo) * temp;
dLdzo->assign(dLdzoMulTemp);
delete dLdzoMulTemp;
// dcdcI
NDArray *dcdcI = f->dup();
// take into account possible deposit from clipping derivative
clipDeriv(params[2], *c, *dLdzi, *dLdzf, *dLdzg, *dcdcI);
// dhdc
NDArray *cUlike = c->ulike();
NDArray dhdc = *cUlike;
activationDeriv(c, params[9], params[10], params[11], &dhdc); // [bS, nOut]
NDArray *dhdcMulO = dhdc * (*o);
dhdc.assign(dhdcMulO);
delete dhdcMulO;
if (Wp) {
NDArray *wpThird = (*Wp)({2 * nOut, 3 * nOut});
NDArray *wpFirst = (*Wp)({0, nOut});
NDArray *wpSecond = (*Wp)({nOut, 2 * nOut});
NDArray *dLdzoMulWpThird = (*dLdzo) * (*wpThird);
NDArray *dhdcPlusDLdzoMulWpThird = dhdc + (*dLdzoMulWpThird);
dhdc.assign(dhdcPlusDLdzoMulWpThird);
delete dLdzoMulWpThird;
delete dhdcPlusDLdzoMulWpThird;
NDArray *dLdziMulWpFirst = (*dLdzi) * (*wpFirst); // broadcast [bS, nOut] * nOut
NDArray *dLdzfMulWpSecond = (*dLdzf) * (*wpSecond); // broadcast [bS, nOut] * nOut
NDArray *sumTemp = (*dLdziMulWpFirst) + (*dLdzfMulWpSecond);
NDArray *dcdcIPlusSumTemp = (*dcdcI) + (*sumTemp);
dcdcI->assign(dcdcIPlusSumTemp);
delete dLdziMulWpFirst;
delete dLdzfMulWpSecond;
delete sumTemp;
delete dcdcIPlusSumTemp;
delete wpThird;
delete wpFirst;
delete wpSecond;
}
if (dLdh) *dLdhI += *dLdh;
if (dLdhL) *dLdhI += *dLdhL;
if (dLdcL) *dLdcI += *dLdcL;
NDArray *dLdhIMulDhdc = (*dLdhI) * dhdc;
*dLdcI += *dLdhIMulDhdc;
delete dLdhIMulDhdc;
NDArray *dLdziMulDLdcI = (*dLdzi) * (*dLdcI); // [bS, nOut](or[nOut])
dLdzi->assign(dLdziMulDLdcI);
delete dLdziMulDLdcI;
NDArray *dLdzfMulDLdcI = (*dLdzf) * (*dLdcI); // [bS, nOut](or[nOut])
dLdzf->assign(dLdzfMulDLdcI);
delete dLdzfMulDLdcI;
NDArray *dLdzgMulDLdcI = (*dLdzg) * (*dLdcI); // [bS, nOut](or[nOut])
dLdzg->assign(dLdzgMulDLdcI);
delete dLdzgMulDLdcI;
NDArray *dLdzoMulDLdhI = (*dLdzo) * (*dLdhI); // [bS, nOut](or[nOut])
dLdzo->assign(dLdzoMulDLdhI);
delete dLdzoMulDLdhI;
// dLdx
NDArray *WxT = Wx->transpose();
MmulHelper::mmul(&dLdz, WxT,
dLdx); // [bS, 4*nOut] x [4*nOut, nIn] (or [4*nOut] x [4*nOut, nIn]) = [bS, nIn] ( or[nIn] )
// dLdhI
NDArray *WrT = Wr->transpose();
MmulHelper::mmul(&dLdz, WrT,
dLdhI); // [bS, 4*nOut] x [4*nOut, nOut] (or [4*nOut] x [4*nOut, nOut]) = [bS, nOut] ( or[nOut] )
// dLdcI
NDArray *dLdcIMulDcdcI = (*dLdcI) * (*dcdcI);
dLdcI->assign(dLdcIMulDcdcI); // [bS, nOut](or[nOut])
delete dLdcIMulDcdcI;
delete WxT;
delete WrT;
if (x->rankOf() == 1) {
std::vector<sd::LongType> xShape = {nIn, 1};
std::vector<sd::LongType> hIShape = {nOut, 1};
std::vector<sd::LongType> dLdzShape = {1, 4 * nOut};
NDArray *xT = x->reshape(x->ordering(), xShape); // [nIn] -> [nIn, 1]
NDArray *hIT = hI->reshape(hI->ordering(), hIShape); // [nOut] -> [nOut, 1]
NDArray *dLdzR = dLdz.reshape(dLdz.ordering(), dLdzShape); // [nOut] -> [1, 4*nOut]
// dLdWx
NDArray *xTMulDLdzR = mmul(*xT, *dLdzR); // [nIn, 1] x [1, 4*nOut] = [nIn, 4*nOut]
*dLdWx += *xTMulDLdzR;
delete xTMulDLdzR;
// dLdWr
NDArray *hITMulDLdzR = mmul(*hIT, *dLdzR); // [nOut, 1] x [1, 4*nOut] = [nOut, 4*nOut]
*dLdWr += *hITMulDLdzR;
delete hITMulDLdzR;
delete xT;
delete hIT;
delete dLdzR;
} else {
NDArray *xT = x->transpose();
NDArray *hIT = hI->transpose();
// dLdWx
NDArray *xTMulDLdz = mmul(*xT, dLdz); // [nIn, bS] x [bS, 4*nOut] = [nIn, 4*nOut]
*dLdWx += *xTMulDLdz;
delete xTMulDLdz;
// dLdWr
NDArray *hITMulDLdz = mmul(*hIT, dLdz); // [nOut, bS] x [bS, 4*nOut] = [nOut, 4*nOut]
*dLdWr += *hITMulDLdz;
delete hITMulDLdz;
delete xT;
delete hIT;
}
// dLdb
if (b && x->rankOf() == 1)
*dLdb += dLdz; // [4*nOut]
else if (b) {
std::vector<sd::LongType> dims = {0};
NDArray *dLdzReduced = dLdz.reduceAlongDimension(reduce::Sum, &dims); // [bS, 4*nOut] -> reduce -> [4*nOut]
*dLdb += *dLdzReduced;
delete dLdzReduced;
}
// dLdWp
if (Wp && x->rankOf() == 1) {
NDArray *firstOut = (*dLdWp)({0, nOut});
NDArray *secondOut = (*dLdWp)({nOut, 2 * nOut});
NDArray *thirdOut = (*dLdWp)({2 * nOut, 3 * nOut});
NDArray *dLdziMulCI = (*dLdzi) * (*cI); // [nOut]
*firstOut += *dLdziMulCI;
delete dLdziMulCI;
NDArray *dLdzfMulCI = (*dLdzf) * (*cI); // [nOut]
*secondOut += *dLdzfMulCI;
delete dLdzfMulCI;
NDArray *dLdzoMulC = (*dLdzo) * (*c); // [nOut]
*thirdOut += *dLdzoMulC;
delete dLdzoMulC;
delete firstOut;
delete secondOut;
delete thirdOut;
} else if (Wp) {
std::vector<sd::LongType> shape = {nOut};
NDArray temp2(Wp->ordering(), shape, Wp->dataType(), Wp->getContext());
std::vector<sd::LongType> dims = {0};
NDArray *firstOut = (*dLdWp)({0, nOut});
NDArray *secondOut = (*dLdWp)({nOut, 2 * nOut});
NDArray *thirdOut = (*dLdWp)({2 * nOut, 3 * nOut});
NDArray *dLdziMulCI = (*dLdzi) * (*cI);
dLdziMulCI->reduceAlongDimension(reduce::Sum, &temp2, &dims); // [bS, nOut] -> reduce -> [nOut]
*firstOut += temp2;
delete dLdziMulCI;
NDArray *dLdzfMulCI = (*dLdzf) * (*cI);
dLdzfMulCI->reduceAlongDimension(reduce::Sum, &temp2, &dims); // [bS, nOut] -> reduce -> [nOut]
*secondOut += temp2;
delete dLdzfMulCI;
NDArray *dLdzoMulC = (*dLdzo) * (*c);
dLdzoMulC->reduceAlongDimension(reduce::Sum, &temp2, &dims); // [bS, nOut] -> reduce -> [nOut]
*thirdOut += temp2;
delete dLdzoMulC;
delete firstOut;
delete secondOut;
delete thirdOut;
}
delete zUlike;
delete cUlike;
delete dLdzoUlike;
delete dcdcI;
}
//////////////////////////////////////////////////////////////////////////
void lstmLayerTimeLoop(NDArray* x, NDArray* Wx, NDArray* Wr, NDArray* b, NDArray* seqLen,
NDArray* hI, NDArray* cI, NDArray* Wp, const std::vector<float>& params,
const bool forward, NDArray* h, NDArray* hL, NDArray* cL) {
// INPUTS:
// x - current input [sL, bS, nIn], [bS, sL, nIn], [bS, nIn, sL],
// Wx - input weights [nIn, 4*nOut]
// Wr - recurrent weights [nOut, 4*nOut]
// b - biases [4*nOut], optional, may be nullptr
// seqLen - [bS], optional, may be nullptr
// hI - initial output [bS, nOut], optional, may be nullptr
// cI - initial cell state at time t-1 [bS, nOut], optional, may be nullptr
// Wp - peephole weights [3*nOut], optional, may be nullptr
// OUTPUTS:
// h - output [sL, bS, nOut], [bS, sL, nOut], [bS, nOut, sL], optional, may be nullptr
// hL - output at last step [bS, nOut], optional, may be nullptr
// cL - cell state at last step [bS, nOut], optional, may be nullptr
// params = {dataFormat, directionMode, cellClip, gateAct, gateAlpha, gateBeta, cellAct, cellAlpha, cellBeta, outAct,
// outAlpha, outBeta}; dataFormat: 0,3 = [sL, bS, nIn], 1 = [bS, sL ,nIn], 2 = [bS, nIn, sL]
const int dataFormat = params[0];
const int directionMode = params[1];
const sd::LongType sL = dataFormat == 3 ? x->sizeAt(0) : x->sizeAt(dataFormat);
const sd::LongType bS = dataFormat == 1 || dataFormat == 2 ? x->sizeAt(0) : x->sizeAt(1);
const sd::LongType nOut = Wx->sizeAt(-1) / 4;
std::vector<sd::LongType> shapeOut = {bS, nOut};
const auto type = h ? h->dataType() : (hL ? hL->dataType() : cL->dataType());
auto h0 = const_cast<NDArray*>(hI);
if (!hI) {
h0 = new NDArray(x->ordering(), shapeOut, type, x->getContext());
h0->nullify();
}
auto c0 = const_cast<NDArray*>(cI);
if (!cI) {
c0 = new NDArray(x->ordering(), shapeOut, type, x->getContext());
c0->nullify();
}
auto ct = cL;
if (!cL) ct = new NDArray(x->ordering(), shapeOut, type, x->getContext());
auto ht = hL;
if (!h && !hL) ht = new NDArray(x->ordering(), shapeOut, type, x->getContext());
// create sets of required (depends on seqLen presence) sub-arrays
std::vector<sd::LongType> *dims;
ResultSet *xSet(nullptr), *hSet(nullptr), *h0Set(nullptr), *c0Set(nullptr), *htSet(nullptr), *ctSet(nullptr);
if (!seqLen) {
std::vector<sd::LongType> dims2 = {dataFormat < 3 ? dataFormat : 0};
dims = ShapeUtils::evalDimsToExclude(x->rankOf(),
dims2.size(),dims2.data()); // points on bS and nIn/nOut axes
xSet = new ResultSet(x->allTensorsAlongDimension(*dims)); // sub-arrays with shape [bS, nIn]
if (h) hSet = new ResultSet(h->allTensorsAlongDimension(*dims)); // sub-arrays with shape [bS, nOut]
} else {
dims = dataFormat == 2 ? new std::vector<sd::LongType>({1}) : new std::vector<sd::LongType>({2}); // points on nIn/nOut axis
xSet = new ResultSet(x->allTensorsAlongDimension(*dims)); // sub-arrays with shape [nIn]
h0Set = new ResultSet(h0->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
c0Set = new ResultSet(c0->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
ctSet = new ResultSet(ct->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
if (h) hSet = new ResultSet(h->allTensorsAlongDimension(*dims)); // sub-arrays with shape [nOut]
if (ht) htSet = new ResultSet(ht->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
delete dims;
}
// loops
if (forward) {
if (!seqLen) {
if (!h) { // seqLen and h are absent
lstmLayerCell(xSet->at(0), Wx, Wr, b, h0, c0, Wp, params, ht, ct); // first time step
for (sd::LongType t = 1; t < sL; ++t)
lstmLayerCell(xSet->at(t), Wx, Wr, b, ht, ct, Wp, params, ht, ct); // rest time steps
} else { // seqLen is absent and h is present
lstmLayerCell(xSet->at(0), Wx, Wr, b, h0, c0, Wp, params, hSet->at(0), ct); // first time step
for (sd::LongType t = 1; t < sL; ++t)
lstmLayerCell(xSet->at(t), Wx, Wr, b, hSet->at(t - 1), ct, Wp, params, hSet->at(t), ct); // rest time steps
if (hL) hL->assign(hSet->at(sL - 1)); // assign last output to hL if it is not nullptr
}
} else {
if (!h) { // seqLen is present and h is absent
for (sd::LongType e = 0; e < bS; ++e) {
const int limit = seqLen->e<int>(e);
if (limit == 0) {
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, 0, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // first time step
for (int t = 1; t < limit; ++t) {
ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, htSet->at(e), ctSet->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // rest time steps
}
}
} else { // seqLen and h are present
for (sd::LongType e = 0; e < bS; ++e) {
int limit = seqLen->e<int>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*h, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto indPrev = getBatchTimeTotalIndex(dataFormat, sL, bS, 0, e);
lstmLayerCell(xSet->at(indPrev), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, hSet->at(indPrev),
ctSet->at(e)); // first time step
for (int t = 1; t < limit; ++t) {
auto indCurr = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(indCurr), Wx, Wr, b, hSet->at(indPrev), ctSet->at(e), Wp, params, hSet->at(indCurr),
ctSet->at(e)); // rest time steps
indPrev = indCurr;
}
if (hL) htSet->at(e)->assign(hSet->at(indPrev)); // assign last output to hL if hL is not nullptr
if (limit != sL)
tensorAlongTimeBatchDims(*h, dataFormat, limit, sL, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
}
}
} else { // backward
if (!seqLen) {
if (!h) { // seqLen and h are absent
lstmLayerCell(xSet->at(sL - 1), Wx, Wr, b, h0, c0, Wp, params, ht, ct); // first time step
for (sd::LongType t = sL - 2; t >= 0; --t)
lstmLayerCell(xSet->at(t), Wx, Wr, b, ht, ct, Wp, params, ht, ct); // rest time steps
} else { // seqLen is absent and h is present
lstmLayerCell(xSet->at(sL - 1), Wx, Wr, b, h0, c0, Wp, params, hSet->at(sL - 1), ct); // first time step
for (sd::LongType t = sL - 2; t >= 0; --t)
lstmLayerCell(xSet->at(t), Wx, Wr, b, hSet->at(t + 1), ct, Wp, params, hSet->at(t), ct); // rest time steps
if (hL) hL->assign(hSet->at(0)); // assign last output to hL if it is not nullptr
}
} else if (directionMode == 1) { // only backward, no bidirectional mode
if (!h) { // h is absent and seqLen is present
for (sd::LongType e = 0; e < bS; ++e) {
const int limit = seqLen->e<int>(e);
if (limit == 0) {
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, sL - 1, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // first time step
for (sd::LongType t = sL - 2; t >= sL - limit; --t) {
ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, htSet->at(e), ctSet->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // rest time steps
}
}
} else { // seqLen and h are present
for (sd::LongType e = 0; e < bS; ++e) {
int limit = seqLen->e<int>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*h, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto indPrev = getBatchTimeTotalIndex(dataFormat, sL, bS, sL - 1, e);
lstmLayerCell(xSet->at(indPrev), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, hSet->at(indPrev),
ctSet->at(e)); // first time step
for (sd::LongType t = sL - 2; t >= sL - limit; --t) {
auto indCurr = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(indCurr), Wx, Wr, b, hSet->at(indPrev), ctSet->at(e), Wp, params, hSet->at(indCurr),
ctSet->at(e)); // rest time steps
indPrev = indCurr;
}
if (hL) htSet->at(e)->assign(hSet->at(indPrev)); // assign last output to hL if it is not nullptr
if (limit != sL)
tensorAlongTimeBatchDims(*h, dataFormat, 0, sL - limit, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
}
} else { // backward in bidirectional mode
if (!h) { // h is absent and seqLen is present
for (sd::LongType e = 0; e < bS; ++e) {
const int limit = seqLen->e<int>(e);
if (limit == 0) {
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, limit - 1, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // first time step
for (int t = limit - 2; t >= 0; --t) {
ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(ind), Wx, Wr, b, htSet->at(e), ctSet->at(e), Wp, params, htSet->at(e),
ctSet->at(e)); // rest time steps
}
}
} else { // seqLen and h are present
for (sd::LongType e = 0; e < bS; ++e) {
int limit = seqLen->e<int>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*h, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
if (cL) ctSet->at(e)->nullify();
if (hL) htSet->at(e)->nullify();
continue;
}
auto indPrev = getBatchTimeTotalIndex(dataFormat, sL, bS, limit - 1, e);
lstmLayerCell(xSet->at(indPrev), Wx, Wr, b, h0Set->at(e), c0Set->at(e), Wp, params, hSet->at(indPrev),
ctSet->at(e)); // first time step
for (int t = limit - 2; t >= 0; --t) {
auto indCurr = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
lstmLayerCell(xSet->at(indCurr), Wx, Wr, b, hSet->at(indPrev), ctSet->at(e), Wp, params, hSet->at(indCurr),
ctSet->at(e)); // rest time steps
indPrev = indCurr;
}
if (hL) htSet->at(e)->assign(hSet->at(indPrev)); // assign last output to hL if it is not nullptr
if (limit != sL)
tensorAlongTimeBatchDims(*h, dataFormat, limit, sL, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
}
}
}
delete xSet;
delete hSet;
delete h0Set;
delete c0Set;
delete htSet;
delete ctSet;
if (!hI) delete h0;
if (!cI) delete c0;
if (!cL) delete ct;
if (!h && !hL) delete ht;
}
//////////////////////////////////////////////////////////////////////////
void lstmLayerTimeLoopBp(NDArray* x, NDArray* Wx, NDArray* Wr, NDArray* b, NDArray* seqLen, NDArray* hI, NDArray* cI,
NDArray* Wp, NDArray* dLdh, NDArray* dLdhL, NDArray* dLdcL,
const std::vector<float>& params, const bool forward, NDArray* dLdx, NDArray* dLdWx,
NDArray* dLdWr, NDArray* dLdb, NDArray* dLdhI, NDArray* dLdcI, NDArray* dLdWp) {
// INPUTS:
// x - current input [sL, bS, nIn], [bS, sL, nIn], [bS, nIn, sL],
// Wx - input weights [nIn, 4*nOut]
// Wr - recurrent weights [nOut, 4*nOut]
// b - biases [4*nOut], optional, may be nullptr
// seqLen - [bS], optional, may be nullptr
// hI - initial output [bS, nOut], optional, may be nullptr
// cI - initial cell state at time t-1 [bS, nOut], optional, may be nullptr
// Wp - peephole weights [3*nOut], optional, may be nullptr
// dLdh - gradient vs. output [sL, bS, nOut], [bS, sL, nOut], [bS, nOut, sL], optional, may be nullptr
// dLdhL - gradient vs. output at last time step [bS, nOut], optional, may be nullptr
// dLdcL - gradient vs. cell state at last time step [bS, nOut], optional, may be nullptr
// OUTPUTS:
// dLdx - gradient vs. input [sL, bS, nIn], [bS, sL, nIn], [bS, nIn, sL]
// dLdWx - gradient vs. input weights [nIn, 4*nOut]
// dLdWr - gradient vs. recurrent weights [nOut, 4*nOut]
// dLdb - gradient vs. biases [4*nOut], optional, may be nullptr
// dLdhI - gradient vs. initial output [bS, nOut], optional, may be nullptr
// dLdcI - gradient vs. initial cell state at time t-1 [bS, nOut], optional, may be nullptr
// dLdWp - gradient vs. peephole weights [3*nOut], optional, may be nullptr
// params = {dataFormat, directionMode, cellClip, gateAct, gateAlpha, gateBeta, cellAct, cellAlpha, cellBeta, outAct,
// outAlpha, outBeta}; dataFormat: 0,3 = [sL, bS, nIn], 1 = [bS, sL ,nIn], 2 = [bS, nIn, sL]
const int dataFormat = params[0];
const int directionMode = params[1];
const int sL = dataFormat == 3 ? x->sizeAt(0) : x->sizeAt(dataFormat);
const int bS = dataFormat == 1 || dataFormat == 2 ? x->sizeAt(0) : x->sizeAt(1);
const int nOut = Wx->sizeAt(-1) / 4;
const auto type = dLdh ? dLdh->dataType() : (dLdhL ? dLdhL->dataType() : dLdcL->dataType());
std::vector<sd::LongType> shape = {bS, nOut};
auto dLdh0 = dLdhI;
if (!hI)
dLdh0 = new NDArray(x->ordering(), shape, type,
x->getContext()); // this constructor nullifies array automatically
auto dLdc0 = dLdcI;
if (!cI)
dLdc0 = new NDArray(x->ordering(), shape, type,
x->getContext()); // this constructor nullifies array automatically
std::vector<LongType> zShape = {sL, bS, 4 * nOut};
NDArray z(x->ordering(), zShape, type, x->getContext());
NDArray *zUlike2 = z.ulike();
NDArray a = *zUlike2;
std::vector<LongType> hShape = {sL + 1, bS, nOut};
NDArray h(x->ordering(), hShape, type, x->getContext());
NDArray *hUlike = h.ulike();
NDArray c = *hUlike;
// create sets of required (depends on seqLen presence) sub-arrays
std::vector<sd::LongType> *dims;
ResultSet *xSet(nullptr), *dLdxSet(nullptr), *hSet(nullptr), *cSet(nullptr), *zSet(nullptr), *aSet(nullptr),
*dLdhSet(nullptr), *dLdh0Set(nullptr), *dLdc0Set(nullptr), *dLdhLSet(nullptr), *dLdcLSet(nullptr),
*hISet(nullptr), *cISet(nullptr);
if (!seqLen) {
std::vector<sd::LongType> dim = {dataFormat < 3 ? dataFormat : 0};
dims = ShapeUtils::evalDimsToExclude(x->rankOf(),dim.size(),dim.data()); // points on [bS, nIn/nOut]
xSet = new ResultSet(x->allTensorsAlongDimension(*dims)); // sub-arrays with shape [bS, nIn]
dLdxSet = new ResultSet(dLdx->allTensorsAlongDimension(*dims)); // sub-arrays with shape [bS, nIn]
hSet = new ResultSet(h.allTensorsAlongDimension({1, 2})); // sub-arrays with shape [bS, nOut]
cSet = new ResultSet(c.allTensorsAlongDimension({1, 2})); // sub-arrays with shape [bS, nOut]
zSet = new ResultSet(z.allTensorsAlongDimension({1, 2})); // sub-arrays with shape [bS, 4*nOut]
aSet = new ResultSet(a.allTensorsAlongDimension({1, 2})); // sub-arrays with shape [bS, 4*nOut]
if (dLdh) dLdhSet = new ResultSet(dLdh->allTensorsAlongDimension(*dims)); // sub-arrays with shape [bS, nOut]
} else {
dims = dataFormat == 2 ? new std::vector<sd::LongType>({1}) : new std::vector<sd::LongType>({2}); // points on nIn/nOut axis
xSet = new ResultSet(x->allTensorsAlongDimension(*dims)); // sub-arrays with shape [nIn]
dLdxSet = new ResultSet(dLdx->allTensorsAlongDimension(*dims)); // sub-arrays with shape [nIn]
hSet = new ResultSet(h.allTensorsAlongDimension({2})); // sub-arrays with shape [nOut]
cSet = new ResultSet(c.allTensorsAlongDimension({2})); // sub-arrays with shape [nOut]
zSet = new ResultSet(z.allTensorsAlongDimension({2})); // sub-arrays with shape [4*nOut]
aSet = new ResultSet(a.allTensorsAlongDimension({2})); // sub-arrays with shape [4*nOut]
if (hI) hISet = new ResultSet(hI->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
if (cI) cISet = new ResultSet(cI->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
dLdh0Set = new ResultSet(dLdh0->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
dLdc0Set = new ResultSet(dLdc0->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
if (dLdh) dLdhSet = new ResultSet(dLdh->allTensorsAlongDimension(*dims)); // sub-arrays with shape [nOut]
if (dLdhL) dLdhLSet = new ResultSet(dLdhL->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
if (dLdcL) dLdcLSet = new ResultSet(dLdcL->allTensorsAlongDimension({1})); // sub-arrays with shape [nOut]
delete dims;
}
// loops
if (forward) {
if (!seqLen) { // seqLen is absent
if (hI)
hSet->at(0)->assign(hI);
else
hSet->at(0)->nullify();
if (cI)
cSet->at(0)->assign(cI);
else
cSet->at(0)->nullify();
// ff
for (sd::LongType t = 0; t < sL; ++t) {
lstmLayerCell(xSet->at(t), Wx, Wr, b, hSet->at(t), cSet->at(t), Wp, params, zSet->at(t), aSet->at(t),
hSet->at(t + 1), cSet->at(t + 1));
}
// bp
for (sd::LongType t = sL - 1; t >= 0; --t) {
NDArray* dLdhh = dLdh ? dLdhSet->at(t) : nullptr;
NDArray* dLdhhL = (t == sL - 1 && dLdhL) ? dLdhL : nullptr;
NDArray* dLdccL = (t == sL - 1 && dLdcL) ? dLdcL : nullptr;
lstmLayerCellBp(xSet->at(t), Wx, Wr, b, hSet->at(t), cSet->at(t), Wp, dLdhh, dLdhhL, dLdccL, zSet->at(t),
aSet->at(t), cSet->at(t + 1), params, dLdxSet->at(t), dLdWx, dLdWr, dLdh0, dLdc0, dLdb, dLdWp);
}
} else { // seqLen is present
for (sd::LongType e = 0; e < bS; ++e) {
const sd::LongType limit = seqLen->e<sd::LongType>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*dLdx, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
continue;
}
if (hI)
hSet->at(e)->assign(hISet->at(e));
else
hSet->at(e)->nullify();
if (cI)
cSet->at(e)->assign(cISet->at(e));
else
cSet->at(e)->nullify();
// ff
for (sd::LongType t = 0; t < limit; ++t) {
lstmLayerCell(xSet->at(getBatchTimeTotalIndex(dataFormat, sL, bS, t, e)), Wx, Wr, b, hSet->at(t * bS + e),
cSet->at(t * bS + e), Wp, params, zSet->at(t * bS + e), aSet->at(t * bS + e),
hSet->at((t + 1) * bS + e), cSet->at((t + 1) * bS + e));
}
// bp
for (sd::LongType t = limit - 1; t >= 0; --t) {
const auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
NDArray* dLdhh = dLdh ? dLdhSet->at(ind) : nullptr;
NDArray* dLdhhL = (t == limit - 1 && dLdhL) ? dLdhLSet->at(e) : nullptr;
NDArray* dLdccL = (t == limit - 1 && dLdcL) ? dLdcLSet->at(e) : nullptr;
lstmLayerCellBp(xSet->at(ind), Wx, Wr, b, hSet->at(t * bS + e), cSet->at(t * bS + e), Wp, dLdhh, dLdhhL,
dLdccL, zSet->at(t * bS + e), aSet->at(t * bS + e), cSet->at((t + 1) * bS + e), params,
dLdxSet->at(ind), dLdWx, dLdWr, dLdh0Set->at(e), dLdc0Set->at(e), dLdb, dLdWp);
}
if (limit != sL)
tensorAlongTimeBatchDims(*dLdx, dataFormat, limit, sL, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
}
} else { // backward or bidirectional
if (!seqLen) { // backward or bidirectional, seqLen is absent
if (hI)
hSet->at(sL)->assign(hI);
else
hSet->at(sL)->nullify();
if (cI)
cSet->at(sL)->assign(cI);
else
cSet->at(sL)->nullify();
// ff
for (sd::LongType t = sL - 1; t >= 0; --t) {
lstmLayerCell(xSet->at(t), Wx, Wr, b, hSet->at(t + 1), cSet->at(t + 1), Wp, params, zSet->at(t), aSet->at(t),
hSet->at(t), cSet->at(t));
}
// bp
for (sd::LongType t = 0; t < sL; ++t) {
NDArray* dLdhh = dLdh ? dLdhSet->at(t) : nullptr;
NDArray* dLdhhL = (t == 0 && dLdhL) ? dLdhL : nullptr;
NDArray* dLdccL = (t == 0 && dLdcL) ? dLdcL : nullptr;
lstmLayerCellBp(xSet->at(t), Wx, Wr, b, hSet->at(t + 1), cSet->at(t + 1), Wp, dLdhh, dLdhhL, dLdccL,
zSet->at(t), aSet->at(t), cSet->at(t), params, dLdxSet->at(t), dLdWx, dLdWr, dLdh0, dLdc0, dLdb,
dLdWp);
}
} else if (directionMode == 1) { // backward, seqLen is present
for (sd::LongType e = 0; e < bS; ++e) {
const sd::LongType limit = seqLen->e<sd::LongType>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*dLdx, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
continue;
}
if (hI)
hSet->at(sL * bS + e)->assign(hISet->at(e));
else
hSet->at(sL * bS + e)->nullify();
if (cI)
cSet->at(sL * bS + e)->assign(cISet->at(e));
else
cSet->at(sL * bS + e)->nullify();
// ff
for (int t = sL - 1; t >= sL - limit; --t)
lstmLayerCell(xSet->at(getBatchTimeTotalIndex(dataFormat, sL, bS, t, e)), Wx, Wr, b,
hSet->at((t + 1) * bS + e), cSet->at((t + 1) * bS + e), Wp, params, zSet->at(t * bS + e),
aSet->at(t * bS + e), hSet->at(t * bS + e), cSet->at(t * bS + e));
// bp
for (sd::LongType t = sL - limit; t < sL; ++t) {
const auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
NDArray* dLdhh = dLdh ? dLdhSet->at(ind) : nullptr;
NDArray* dLdhhL = (t == sL - limit && dLdhL) ? dLdhLSet->at(e) : nullptr;
NDArray* dLdccL = (t == sL - limit && dLdcL) ? dLdcLSet->at(e) : nullptr;
lstmLayerCellBp(xSet->at(ind), Wx, Wr, b, hSet->at((t + 1) * bS + e), cSet->at((t + 1) * bS + e), Wp, dLdhh,
dLdhhL, dLdccL, zSet->at(t * bS + e), aSet->at(t * bS + e), cSet->at(t * bS + e), params,
dLdxSet->at(ind), dLdWx, dLdWr, dLdh0Set->at(e), dLdc0Set->at(e), dLdb, dLdWp);
}
if (limit != sL)
tensorAlongTimeBatchDims(*dLdx, dataFormat, 0, sL - limit, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
} else { // bidirectional mode, seqLen is present
for (sd::LongType e = 0; e < bS; ++e) {
const int limit = seqLen->e<sd::LongType>(e);
if (limit == 0) {
tensorAlongTimeBatchDims(*dLdx, dataFormat, 0, 0, e, e + 1)
->nullify(); // nullify for given e and whole time range
continue;
}
NDArray *hView = h({limit, limit + 1, e, e + 1, 0, 0});
NDArray *cView = c({limit, limit + 1, e, e + 1, 0, 0});
if (hI)
hView->assign(hISet->at(e));
else
hView->nullify();
if (cI)
cView->assign(cISet->at(e));
else
cView->nullify();
delete hView;
delete cView;
// ff
for (int t = limit - 1; t >= 0; --t)
lstmLayerCell(xSet->at(getBatchTimeTotalIndex(dataFormat, sL, bS, t, e)), Wx, Wr, b,
hSet->at((t + 1) * bS + e), cSet->at((t + 1) * bS + e), Wp, params, zSet->at(t * bS + e),
aSet->at(t * bS + e), hSet->at(t * bS + e), cSet->at(t * bS + e));
// bp
for (sd::LongType t = 0; t < limit; ++t) {
const auto ind = getBatchTimeTotalIndex(dataFormat, sL, bS, t, e);
NDArray* dLdhh = dLdh ? dLdhSet->at(ind) : nullptr;
NDArray* dLdhhL = (t == 0 && dLdhL) ? dLdhLSet->at(e) : nullptr;
NDArray* dLdccL = (t == 0 && dLdcL) ? dLdcLSet->at(e) : nullptr;
lstmLayerCellBp(xSet->at(ind), Wx, Wr, b, hSet->at((t + 1) * bS + e), cSet->at((t + 1) * bS + e), Wp, dLdhh,
dLdhhL, dLdccL, zSet->at(t * bS + e), aSet->at(t * bS + e), cSet->at(t * bS + e), params,
dLdxSet->at(ind), dLdWx, dLdWr, dLdh0Set->at(e), dLdc0Set->at(e), dLdb, dLdWp);
}
if (limit != sL)
tensorAlongTimeBatchDims(*dLdx, dataFormat, limit, sL, e, e + 1)
->nullify(); // nullify for given e and time range [limit, sL)
}
}
}
delete xSet;
delete dLdxSet;
delete hSet;
delete cSet;
delete aSet;
delete zSet;
delete dLdhSet;
delete dLdh0Set;
delete dLdc0Set;
delete dLdhLSet;
delete dLdcLSet;
delete hISet;
delete cISet;
delete zUlike2;
delete hUlike;
if (!hI) delete dLdh0;
if (!cI) delete dLdc0;
}
} // namespace helpers
} // namespace ops
} // namespace sd
//////////////////////////////////////////////////////////////////////////
#endif