docs: public void preApply(Trainable layer, Gradient gradient, int iteration) {
  
          if (layer.getConfig() == null || layer.numParams() == 0) {
              //Layer does not have parameters -> no gradient
              return;
          }
  
          GradientNormalization normalization = layer.getConfig().getGradientNormalization();
          if (normalization == null || normalization == GradientNormalization.None)
              return; //no op
  
          final double threshold = layer.getConfig().getGradientNormalizationThreshold();
          INDArray layerGradientView = layer.getGradientsViewArray();
  
          switch (normalization) {
              case RenormalizeL2PerLayer:
                  if (layerGradientView != null) {
                      double l2 = layerGradientView.norm2Number().doubleValue();
                      if (l2 == 0.0)
                          l2 = 1e-5;  //Avoid 0/0 -> NaN
                      layerGradientView.divi(l2);
                  }
                  break;
              case RenormalizeL2PerParamType:
                  for (INDArray g : gradient.gradientForVariable().values()) {
                      double l2 = Nd4j.getExecutioner().execAndReturn(new Norm2(g)).getFinalResult().doubleValue();
                      if (l2 == 0.0)
                          l2 = 1e-5;  //Avoid 0/0 -> NaN
                      g.divi(l2);
                  }
                  break;
              case ClipElementWiseAbsoluteValue:
                  if (layerGradientView != null) {
                      CustomOp op = DynamicCustomOp.builder("clipbyvalue")
                              .addInputs(layerGradientView)
                              .callInplace(true)
                              .addFloatingPointArguments(-threshold, threshold)
                              .build();
                      Nd4j.getExecutioner().exec(op);
                  }
                  break;
              case ClipL2PerLayer:
                  if (layerGradientView != null) {
                      double layerL2 = layerGradientView.norm2Number().doubleValue();
                      if (layerL2 > threshold) {
                          double scalingFactor = threshold / layerL2; // g = g / l2 * threshold ->
                          layerGradientView.muli(scalingFactor);
                      }
                  }
                  break;
              case ClipL2PerParamType:
                  for (INDArray g : gradient.gradientForVariable().values()) {
                      double l2 = g.norm2Number().doubleValue();
                      if (l2 > threshold) {
                          double scalingFactor = l2 / threshold;
                          g.divi(scalingFactor);
                      }
                  }
                  break;
              default:
                  throw new RuntimeException(
                                  "Unknown (or not implemented) gradient normalization strategy: " + normalization);
          }
      }