docs: @Override
      public void sgeqrf(int M, int N, INDArray A, INDArray R, INDArray INFO) {
          INDArray a = A;
          INDArray r = R;
  
          if (Nd4j.dataType() != DataType.FLOAT)
              log.warn("FLOAT getrf called in DOUBLE environment");
  
          if (A.ordering() == 'c') 
              a = A.dup('f');
          if ( R!=null && R.ordering() == 'c')
              r = R.dup('f');
  
          INDArray tau = Nd4j.createArrayFromShapeBuffer(Nd4j.getDataBufferFactory().createFloat(N),
                  Nd4j.getShapeInfoProvider().createShapeInformation(new long[] {1, N}, A.dataType()).getFirst());
  
          if (Nd4j.getExecutioner() instanceof GridExecutioner)
              ((GridExecutioner) Nd4j.getExecutioner()).flushQueue();
  
          // Get context for current thread
          CudaContext ctx = (CudaContext) allocator.getDeviceContext().getContext();
  
          // setup the solver handles for cuSolver calls
          cusolverDnHandle_t handle = ctx.getSolverHandle();
          cusolverDnContext solverDn = new cusolverDnContext(handle);
  
          // synchronized on the solver
          synchronized (handle) {
              int result = cusolverDnSetStream(new cusolverDnContext(handle), new CUstream_st(ctx.getOldStream()));
              if (result != 0)
                  throw new IllegalStateException("solverSetStream failed");
  
              // transfer the INDArray into GPU memory
              CublasPointer xAPointer = new CublasPointer(a, ctx);
              CublasPointer xTauPointer = new CublasPointer(tau, ctx);
  
              // this output - indicates how much memory we'll need for the real operation
              DataBuffer worksizeBuffer = Nd4j.getDataBufferFactory().createInt(1);
  
              int stat = cusolverDnSgeqrf_bufferSize(solverDn, M, N, 
                          (FloatPointer) xAPointer.getDevicePointer(), M,
                          (IntPointer) worksizeBuffer.addressPointer() // we intentionally use host pointer here
              );
  
  
              if (stat != CUSOLVER_STATUS_SUCCESS) {
                  throw new BlasException("cusolverDnSgeqrf_bufferSize failed", stat);
              }
              int worksize = worksizeBuffer.getInt(0);
              // Now allocate memory for the workspace, the permutation matrix and a return code
              Pointer workspace = new Workspace(worksize * Nd4j.sizeOfDataType());
  
              // Do the actual QR decomp
              stat = cusolverDnSgeqrf(solverDn, M, N, 
                              (FloatPointer) xAPointer.getDevicePointer(), M,
                              (FloatPointer) xTauPointer.getDevicePointer(),
                              new CudaPointer(workspace).asFloatPointer(),
                              worksize,
                              new CudaPointer(allocator.getPointer(INFO, ctx)).asIntPointer()
                              );
              if (stat != CUSOLVER_STATUS_SUCCESS) {
                  throw new BlasException("cusolverDnSgeqrf failed", stat);
              }
              
              allocator.registerAction(ctx, a);
              //allocator.registerAction(ctx, tau);
              allocator.registerAction(ctx, INFO);
              if (INFO.getInt(0) != 0 ) {
                  throw new BlasException("cusolverDnSgeqrf failed on INFO", INFO.getInt(0));
              }
  
              // Copy R ( upper part of Q ) into result
              if( r != null ) {
                  r.assign( a.get( NDArrayIndex.interval( 0, a.columns() ), NDArrayIndex.all() ) ) ; 
  	            
                  INDArrayIndex ix[] = new INDArrayIndex[ 2 ] ;
                  for( int i=1 ; i<Math.min( a.rows(), a.columns() ) ; i++ ) {
                      ix[0] = NDArrayIndex.point( i ) ;
                      ix[1] = NDArrayIndex.interval( 0, i ) ;				
                      r.put(ix, 0) ;
                  }
              }
  
              stat = cusolverDnSorgqr_bufferSize(solverDn, M, N, N,
                          (FloatPointer) xAPointer.getDevicePointer(), M,
                          (FloatPointer) xTauPointer.getDevicePointer(),
                          (IntPointer) worksizeBuffer.addressPointer()
                  );
              worksize = worksizeBuffer.getInt(0);
              workspace = new Workspace(worksize * Nd4j.sizeOfDataType());
  
              stat = cusolverDnSorgqr(solverDn, M, N, N,
                          (FloatPointer) xAPointer.getDevicePointer(), M,
                          (FloatPointer) xTauPointer.getDevicePointer(),
                          new CudaPointer(workspace).asFloatPointer(),
                          worksize,
                          new CudaPointer(allocator.getPointer(INFO, ctx)).asIntPointer()
              );
              if (stat != CUSOLVER_STATUS_SUCCESS) {
                  throw new BlasException("cusolverDnSorgqr failed", stat);
              }            
          }
          allocator.registerAction(ctx, a);
          allocator.registerAction(ctx, INFO);
          //    allocator.registerAction(ctx, tau);
  
          if (a != A)
              A.assign(a);
          if ( r!=null && r != R )
              R.assign(r);
  
          log.info("A: {}", A);
          if( R != null ) log.info("R: {}", R);
      }