Converting Between fmlr Objects

Converting between fmlr objects is simple to do using the various helper functions. Let’s start with a simple matrix on the CPU:

suppressMessages(library(fmlr))

x_cpu = cpumat(2, 2)
x_cpu$fill_linspace(1, 4)

We can distribute the data to an mpimat object using cpu2mpi().

g = grid()
x_mpi = mpimat(g)
cpu2mpi(x_cpu, x_mpi)
x_mpi
## 1.0000 3.0000 
## 2.0000 4.0000

These are completely different kinds of objects, so there is no concept of “shallow copying” here. New storage will be allocated. Also note that running this in one process instead of launching the script with mpirun isn’t actually that useful (or even properly distributed). We only provide this for demonstration. See the article on backends for more information.

Converting between objects works essentially the same as this for the various choices of backends. You just have to pick the right copy function.

  • CPU objects
    • cpu2cpu() copy data between CPU objects (more on this in the next section)
  • GPU objects
    • cpu2gpu() copy CPU matrix/vector data to a GPU matrix/vector
    • gpu2cpu() copy GPU matrix/vector data to a CPU matrix/vector
    • gpu2gpu() copy data between GPU objects
  • MPI objects
    • cpu2mpi() distribute CPU matrix to an MPI matrix
    • mpi2mpi() copy data between MPI objects

It’s important to note that you have to initialize the object you wish to copy into first. That object can be just a “placeholder” (i.e., it can have 0 rows and columns). Some objects have additional objects they have to maintain (the return of grid() for mpimat objects and the return of card() for gpumat and gpuvec objects). Since we can’t guess these, you have to specify them in the object creation, as in the example above where we called mpimat(g). There was no underlying data allocated to the object until cpu2mpi() was called. Of course, if you had already allocated the necessary data, then cpu2mpi() would not have done any memory operations beyond the copy.

Fundamental Type

When we talk about “fundamental type” of an object, we mean the kind of data the object represents. Right now there are three supported fundamental types for all objects:

  • int - 32-bit signed integer
  • float - single precision
  • double - double precision

In the future, we plan to support __half for GPU objects, as the C++ fml does.

R supports int and double in their overloaded numeric type. The float package adds some support for float data. R also has some skeleton support for double complex for computing eigenvalues of non-symmetric matrices. We do not plan to ever support complex types.

In fmlr, all methods support float and double. Some methods will support int, but most will not. This is a major departure from normal R behavior. For example, if you wanted to compute the SVD of an integer matrix, R will automatically promote the int data to double without asking or telling you, and then perform the SVD on the double copy. This is because it doesn’t really make any sense to talk about an SVD of integer data most of the time. However, in fmlr, the linalg_svd() function will just throw an error:

x = cpumat(3, 2, "int")
x$fill_linspace(1, 6)

s = cpuvec(type="int")

linalg_svd(x, s)
## Error in linalg_svd(x, s): unsupported fundamental type

If you really do need to compute on the integer data, then you will need to manually copy it. Fortunately, this is easy. We just use the cpu2cpu() helper:

y = cpumat(type="f")
cpu2cpu(x, y)
y$info()
## # cpumat 3x2 type=f
y
## 1.0000 4.0000 
## 2.0000 5.0000 
## 3.0000 6.0000

Since y has fundamental type float, it is using exactly as much memory as its int counterpart in x. If we had chosen double then it would require twice as much memory.

From here, computing the SVD is easy:

s = cpuvec(type="f")
linalg_svd(y, s)
s$info()
## # cpuvec 2 type=f
s
## 9.5080 0.7729

One other important caveat is that we can’t mix fundamental types for most methods. The converters, for example cpu2cpu() as seen above, can mix fundamental types. But the compute functions like linalg_svd() will throw an error if we try to mix fundamental types.

As a final note, if we convert an fmlr object with fundamental type float to a native R object, it will be wrapped as a float32 object from the float package:

s$to_robj()
## # A float32 vector: 2
## [1] 9.50803 0.77287

Converting R Objects Into fmlr Objects

Let’s start with a simple R matrix:

x = matrix(sqrt(1:6), 3, 2)
x
##          [,1]     [,2]
## [1,] 1.000000 2.000000
## [2,] 1.414214 2.236068
## [3,] 1.732051 2.449490

We can convert this into an fmlr matrix using the object’s from_robj() method. For simplicity, we’ll just use a CPU matrix:

x_cpu = cpumat()$from_robj(x)
x_cpu
## 1.0000 2.0000 
## 1.4142 2.2361 
## 1.7321 2.4495

This copy is a “deep copy”, in that the memory of the R object x and the fmlr object x_cpu are different. Observe:

x_cpu$fill_zero()
x_cpu
## 0.0000 0.0000 
## 0.0000 0.0000 
## 0.0000 0.0000
x
##          [,1]     [,2]
## [1,] 1.000000 2.000000
## [2,] 1.414214 2.236068
## [3,] 1.732051 2.449490

The as_cpumat()function offers a shorthand for this operation, so you do not have to manually create the skeleton object:

x_cpu = as_cpumat(x)
x
##          [,1]     [,2]
## [1,] 1.000000 2.000000
## [2,] 1.414214 2.236068
## [3,] 1.732051 2.449490

However, for CPU matrices/vectors, there is a way to shallow copy. This allows us to create an fmlr object that merely inherits the same pointer that R is using. We can do this via the inherit() method, or by setting the argument copy=FALSE in as_cpumat(). However, care must be exercised when doing this because it can produce surprising results if you are not careful:

x_cpu = as_cpumat(x, copy=FALSE)
x_cpu$fill_zero()
x
##      [,1] [,2]
## [1,]    0    0
## [2,]    0    0
## [3,]    0    0

Also, do not allow a reallocation to trigger on the inherited pointer as this will crash your R session.

All of the above is likewise true for numeric R vectors and cpuvec objects. Likewise, we can inherit shallow copies from float32 objects (those from the float package).

For gpumat and gpuvec, we can not inherit data without a copy - because native R matrices/vectors do not live on GPUs. To copy R matrix data to a GPU, we can use cpu2gpu(). But first, we have to have the R data represented as an fmlr object. Using as_cpumat() as above:

x = matrix(sqrt(1:6), 3, 2)
x_cpu = as_cpumat(x, copy=FALSE)

c = card()
x_gpu = gpumat(c, type="float")
cpu2gpu(x_cpu, x_gpu)
x_gpu$info()
## # gpumat 3x2 type=f 
x_gpu
## 1.0000 2.0000 
## 1.4142 2.2361 
## 1.7321 2.4495

Note that the objects are actually of different fundamental type. The data we inherit from R is double, while the data on the GPU is float.

Converting fmlr Objects Into R Objects

We have already seen several examples of using the to_robj() method to copy data back to an R object. This operates via a deep copy.

For cases like the above where we had a shallow copy of an R object in a cpumat object, we can, for example, copy back from GPU memory to CPU memory without a copy. Observe:

x_gpu$fill_eye()
gpu2cpu(x_gpu, x_cpu)
x
##      [,1] [,2]
## [1,]    1    0
## [2,]    0    1
## [3,]    0    0

If x and x_cpu had used different memory, we could have copied back via:

x = x_gpu$to_robj()