docs: @Override
      public void shuffle(List<INDArray> arrays, Random rnd, List<int[]> dimensions) {
          // no dimension - no shuffle
          if (dimensions == null || dimensions.size() == 0)
              throw new RuntimeException("Dimension can't be null or 0-length");
  
          if (arrays == null || arrays.size() == 0)
              throw new RuntimeException("No input arrays provided");
  
          if (dimensions.size() > 1 && arrays.size() != dimensions.size())
              throw new IllegalStateException("Number of dimensions do not match number of arrays to shuffle");
  
          Nd4j.getExecutioner().push();
  
          // first we build TAD for input array and dimensions
  
          AtomicAllocator allocator = AtomicAllocator.getInstance();
  
          CudaContext context = null;
  
          for (int x = 0; x < arrays.size(); x++) {
              context = allocator.getFlowController().prepareAction(arrays.get(x));
          }
  
          val zero = arrays.get(0);
          int tadLength = 1;
          if (zero.rank() > 1)
              for (int i = 0; i < dimensions.get(0).length; i++) {
                  tadLength *= zero.shape()[dimensions.get(0)[i]];
              }
  
          val numTads = zero.length() / tadLength;
  
          val map = ArrayUtil.buildInterleavedVector(rnd, (int) numTads);
  
          val shuffle = new CudaIntDataBuffer(map);
  
          val shuffleMap = allocator.getPointer(shuffle, context);
  
          val extras = new PointerPointer(null, // not used
                          context.getOldStream(), allocator.getDeviceIdPointer());
  
  
          long[] hPointers = new long[arrays.size()];
          long[] xPointers = new long[arrays.size()];
          long[] xShapes = new long[arrays.size()];
          long[] tadShapes = new long[arrays.size()];
          long[] tadOffsets = new long[arrays.size()];
  
          for (int i = 0; i < arrays.size(); i++) {
              val array = arrays.get(i);
  
              val x = AtomicAllocator.getInstance().getPointer(array, context);
              val xShapeInfo = AtomicAllocator.getInstance().getPointer(array.shapeInfoDataBuffer(), context);
  
  
              val tadManager = Nd4j.getExecutioner().getTADManager();
  
              int[] dimension = dimensions.size() > 1 ? dimensions.get(i) : dimensions.get(0);
  
              val tadBuffers = tadManager.getTADOnlyShapeInfo(array, dimension);
  
  //            log.info("Original shape: {}; dimension: {}; TAD shape: {}", array.shapeInfoDataBuffer().asInt(), dimension, tadBuffers.getFirst().asInt());
  
              val tadShapeInfo = AtomicAllocator.getInstance().getPointer(tadBuffers.getFirst(), context);
  
              val offsets = tadBuffers.getSecond();
  
              if (zero.rank() != 1 && offsets.length() != numTads)
                  throw new ND4JIllegalStateException("Can't symmetrically shuffle arrays with non-equal number of TADs");
  
              val tadOffset = AtomicAllocator.getInstance().getPointer(offsets, context);
  
              hPointers[i] = AtomicAllocator.getInstance().getHostPointer(array.shapeInfoDataBuffer()).address();
              xPointers[i] = x.address();
              xShapes[i] = xShapeInfo.address();
              tadShapes[i] = tadShapeInfo.address();
              tadOffsets[i] = tadOffset.address();
          }
  
  
          val hostPointers = new LongPointer(hPointers);
          val hosthost = new PointerPointerWrapper(hostPointers);
          val tempX = new CudaDoubleDataBuffer(arrays.size());
          val tempShapes = new CudaDoubleDataBuffer(arrays.size());
          val tempTAD = new CudaDoubleDataBuffer(arrays.size());
          val tempOffsets = new CudaDoubleDataBuffer(arrays.size());
  
          AtomicAllocator.getInstance().memcpyBlocking(tempX, new LongPointer(xPointers), xPointers.length * 8, 0);
          AtomicAllocator.getInstance().memcpyBlocking(tempShapes, new LongPointer(xShapes), xPointers.length * 8, 0);
          AtomicAllocator.getInstance().memcpyBlocking(tempTAD, new LongPointer(tadShapes), xPointers.length * 8, 0);
          AtomicAllocator.getInstance().memcpyBlocking(tempOffsets, new LongPointer(tadOffsets), xPointers.length * 8, 0);
  
          nativeOps.shuffle(extras,
                              null,
                              hosthost,
                              new PointerPointer(allocator.getPointer(tempX, context)),
                              new PointerPointer(allocator.getPointer(tempShapes, context)),
                              null,
                              null,
                              new PointerPointer(allocator.getPointer(tempX, context)),
                              new PointerPointer(allocator.getPointer(tempShapes, context)), arrays.size(),
                              (IntPointer) shuffleMap, new PointerPointer(allocator.getPointer(tempTAD, context)),
                              new PointerPointer(allocator.getPointer(tempOffsets, context)));
  
          for (int f = 0; f < arrays.size(); f++) {
              allocator.getFlowController().registerAction(context, arrays.get(f));
          }
  
  
          // just to keep reference
          shuffle.address();
          hostPointers.address();
  
          tempX.dataType();
          tempShapes.dataType();
          tempOffsets.dataType();
          tempTAD.dataType();
      }