code: private Pair<Gradient, INDArray> getGradientsAndDelta(INDArray preOut, LayerWorkspaceMgr workspaceMgr) {
          ILossFunction lossFunction = layerConf().getLossFn();
          INDArray labels2d = getLabels2d(workspaceMgr, ArrayType.BP_WORKING_MEM);
          //INDArray delta = lossFunction.computeGradient(labels2d, preOut, layerConf().getActivationFunction(), maskArray);
          INDArray delta = lossFunction.computeGradient(labels2d, preOut, layerConf().getActivationFn(), maskArray);
  
          Gradient gradient = new DefaultGradient();
  
          INDArray weightGradView = gradientViews.get(DefaultParamInitializer.WEIGHT_KEY);
          Nd4j.gemm(input.castTo(weightGradView.dataType()), delta, weightGradView, true, false, 1.0, 0.0); //Equivalent to:  weightGradView.assign(input.transpose().mmul(delta));         //TODO can we avoid cast?
          gradient.gradientForVariable().put(DefaultParamInitializer.WEIGHT_KEY, weightGradView);
  
          if(hasBias()){
              INDArray biasGradView = gradientViews.get(DefaultParamInitializer.BIAS_KEY);
              delta.sum(biasGradView, 0); //biasGradView is initialized/zeroed first in sum op
              gradient.gradientForVariable().put(DefaultParamInitializer.BIAS_KEY, biasGradView);
          }
  
          delta = workspaceMgr.leverageTo(ArrayType.ACTIVATION_GRAD, delta);
          return new Pair<>(gradient, delta);
      }
docs: Returns tuple: {Gradient,Delta,Output} given preOut