/* ****************************************************************************** * * * 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 #if NOT_EXCLUDED(OP_lstmLayer) #include #include #include #include #include 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(alpha), static_cast(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(alpha), static_cast(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(i); z->p( i, alpha * beta * (1.f - sd::math::sd_tanh(val) * sd::math::sd_tanh(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(x->e(i)); z->p(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(i); if (val == -clipVal || val == clipVal) { z0.p(i, 0.f); z1.p(i, 0.f); z2.p(i, 0.f); z3.p(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& 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& 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& 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 xShape = {nIn, 1}; std::vector hIShape = {nOut, 1}; std::vector 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 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 shape = {nOut}; NDArray temp2(Wp->ordering(), shape, Wp->dataType(), Wp->getContext()); std::vector 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& 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 shapeOut = {bS, nOut}; const auto type = h ? h->dataType() : (hL ? hL->dataType() : cL->dataType()); auto h0 = const_cast(hI); if (!hI) { h0 = new NDArray(x->ordering(), shapeOut, type, x->getContext()); h0->nullify(); } auto c0 = const_cast(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 *dims; ResultSet *xSet(nullptr), *hSet(nullptr), *h0Set(nullptr), *c0Set(nullptr), *htSet(nullptr), *ctSet(nullptr); if (!seqLen) { std::vector 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({1}) : new std::vector({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(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(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(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(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(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(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& 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 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 zShape = {sL, bS, 4 * nOut}; NDArray z(x->ordering(), zShape, type, x->getContext()); NDArray *zUlike2 = z.ulike(); NDArray a = *zUlike2; std::vector 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 *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 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({1}) : new std::vector({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(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(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(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