Skip to content
jcanny edited this page Jun 15, 2014 · 16 revisions

Table of Contents

Testing Installation and Performance

On startup you should see something like this:

$ bidmat
Loading /code/BIDMat/lib/bidmat_init.scala...
import BIDMat.{SBMat, CMat, CSMat, DMat, Dict, IDict, FMat, GMat, GIMat, GSMat, HMat, IMat, Mat, SMat, SDMat}
import BIDMat.MatFunctions._
import BIDMat.SciFunctions._
import BIDMat.Solvers._
import BIDMat.Plotting._
1 CUDA device found, CUDA version 5.5

Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> 

indicating that the main classes loaded and that a GPU was found.

Now type the following to create a random matrix:

scala> val a = rand(3000,3000)
a: BIDMat.FMat =
   0.67636   0.58297   0.21284   0.97019  0.087385   0.76965   0.47488...
   0.31279   0.59530   0.25992   0.51720  0.087299   0.80404   0.83870...
   0.10547   0.55967   0.44922   0.14059   0.16301  0.044989   0.56812...
   0.83065   0.52546   0.91525   0.16164   0.54631   0.55759   0.21399...
   0.15675  0.063413   0.78380   0.41679   0.36221   0.78108   0.60856...
   0.69485   0.72586   0.72184   0.64364  0.050727   0.98329   0.11313...
   0.88596   0.90342   0.47425   0.51451   0.11018   0.87259   0.72264...
   0.84817   0.11027   0.19484  0.055551   0.93872   0.22773   0.30985...
        ..        ..        ..        ..        ..        ..        ..
This uses Intel MKL to generate the matrix, so if it works it means the MKL native code has correctly loaded. Now try:
scala> flip; val b = a*a; val ff=gflop
b: BIDMat.FMat =
  751.01  751.43  750.92  747.53  750.46  744.76  750.68  750.70  743.39...
  741.96  751.55  750.10  750.51  741.24  743.60  748.20  757.36  751.57...
  733.65  751.63  742.97  746.11  738.84  736.44  752.63  745.63  738.64...
  757.46  781.16  768.23  760.90  766.52  764.85  767.87  762.57  756.51...
  749.71  754.50  744.89  747.67  752.67  747.45  744.02  742.98  751.14...
  738.64  753.36  745.65  750.14  737.78  750.55  741.35  753.49  747.13...
  744.36  761.97  749.35  753.30  747.55  742.92  745.93  753.28  747.26...
  764.38  768.82  767.72  775.11  767.92  768.01  772.17  775.47  762.84...
      ..      ..      ..      ..      ..      ..      ..      ..      ..

ff: (Float, Float) = (183.05086,0.295)

This multiplies a by itself and stores the result in b. The functions flip and gflop compute the time and number of Gflops for this call. These values show that the calculation took about 0.3 seconds and netted 183 Gflops. Your machine may be slower or faster (this was for a core i7-2600), but something much slower than this indicates a problem.

Now to test the GPU, do:

scala> val g = grand(3000,3000)
g: BIDMat.GMat =
   0.40568   0.11267  0.050967   0.38183   0.91456   0.40912   0.21009...
   0.59785   0.41277   0.71035   0.68531   0.43714   0.52936   0.53473...
   0.11102  0.015594   0.25566   0.25119   0.77686   0.62957   0.63329...
   0.32611  0.082551   0.45868   0.30447  0.088703   0.87002   0.24415...
   0.97801   0.23068   0.14317   0.82725   0.53067   0.91981   0.72807...
   0.46033   0.77612   0.69303   0.15826   0.33749   0.82725   0.68517...
   0.96959   0.40553   0.22919   0.71966   0.74566   0.95769   0.31992...
   0.17353   0.38096   0.60261   0.77322   0.99008   0.95707   0.69294...
        ..        ..        ..        ..        ..        ..        ..


scala> flip; val h = g*g; val ff=gflop
h: BIDMat.GMat =
  759.61  759.56  752.52  748.68  750.42  766.10  751.53  746.98  758.98...
  741.68  754.99  743.98  744.17  748.52  756.97  752.39  752.53  760.33...
  741.08  740.15  745.79  747.47  750.61  761.28  738.41  748.33  752.16...
  749.40  752.29  757.17  753.22  757.10  762.86  757.24  753.07  768.75...
  726.33  727.78  728.81  730.70  723.53  740.67  729.84  733.18  731.35...
  753.98  754.07  761.71  749.98  759.18  762.26  750.86  754.75  757.96...
  743.21  738.98  746.01  742.26  752.80  750.21  743.48  741.29  750.70...
  744.79  752.71  756.96  755.16  767.80  780.20  759.22  755.62  762.06...
      ..      ..      ..      ..      ..      ..      ..      ..      ..

ff: (Float, Float) = (782.6087,0.069)

If this works, it means the CUDA libs have loaded correctly. This creates a random matrix in GPU memory, then multiplies it by itself and stores the result in h (which will also be in GPU memory). This time the calculation netted about 780 Gflops. You can also do:

scala> flip; h ~ g*g; val ff=gflop
ff: (Float, Float) = (964.2857,0.056)

Which stores the result in the existing matrix h instead of creating a new one. As you can see, this speeds things up and nets nearly a teraflop. The GPU is a GTX-680.

Finally, try:

scala> exp(g,h)
res1: BIDMat.GMat =
  1.5003  1.7436  1.9892  1.6319  2.1397  2.0146  2.4259  1.0441  1.1706...
  1.8182  2.6013  1.9207  1.0265  2.2031  1.3057  1.4779  1.8867  1.3123...
  1.1174  1.6790  1.1055  1.0624  2.2830  1.5482  1.2922  2.3567  1.0348...
  1.3856  2.7165  2.5324  1.6711  1.5585  1.4704  2.3262  2.2199  2.1263...
  2.6592  1.8933  1.1651  1.8192  1.0937  1.4985  2.5894  2.5275  2.0825...
  1.5846  1.0513  1.0534  1.7084  2.4763  1.1594  1.7666  2.5014  1.2050...
  2.6369  1.6908  2.6367  1.4617  1.9554  1.7544  1.1288  2.5352  1.9009...
  1.1895  1.1076  2.5527  1.3663  2.0377  2.1017  1.1229  1.9415  2.0800...
      ..      ..      ..      ..      ..      ..      ..      ..      ..

This tests that out the BIDMat custom CUDA kernels loaded correctly.

Define Some Matrices

Type the following to create an integer matrix:

scala> val a = 1\2\3 on 4\5\6
a: BIDMat.IMat =
   1   2   3
   4   5   6

"IMat" is an integer (32bit) dense matrix class. The operator \ is horizontal concatenation between matrices, and "on" is a vertical concatenation operator. "\" has higher precedence than "on", so the rows group together. The input arguments were integers, not matrices, but the library includes an implicit cast from Int to IMat. There is an instance of the \ operator that takes an integer as a second argument, so the input arguments to \ are "close enough" (only one implicit cast away) to produce a matrix output.

Try now

scala> val a = 1.0\2\3 on 4.0\5\6
a: BIDMat.DMat =
   1   2   3
   4   5   6

"DMat" is a double precision floating-point dense matrix class. The \ operator exists in the DMat class, and there is an implicit conversion from Doubles to DMat, so the first argument converts up to a DMat. The arguments are once again close enough to match and produce a DMat output. Its safer to stick to arguments of one type however.

scala> val a = 1.0f\2\3 on 4f\5\6
a: BIDMat.FMat =
   1   2   3
   4   5   6

"FMat" is a single precision floating-point dense matrix class. In contrast to Matlab and many other tools, most BIDMat routines return single-precision values and FMat's. This is for size and speed. Float matrices consume half the space and calculations are much faster. On a commodity GPU they are 4-10 times faster. To generate an FMat, we had to start with floating point values. In the base Scala language, 1.0 is a double-precision value, while 1.0f is single-precision.

A few other options:

scala> val b = row(3,5,8,9)
b: BIDMat.FMat =
   3   5   8   9

scala> val c = irow(1,1,1)
c: BIDMat.IMat =
   1   1   1

scala> val d = drow(1,2,3)
d: BIDMat.DMat =
   1   2   3

"row" takes a variable number of arguments and returns a 1xn FMat. "irow" returns a 1xn IMat, and "drow" returns a DMat.

You can also use ranges in two ways:

scala> irow(0 to 5)
res4: BIDMat.IMat =
   0   1   2   3   4   5

scala> (1 to 5) on (0 to 4)
res5: BIDMat.IMat =
   1   2   3   4   5
   0   1   2   3   4

i.e. ranges can be used as arguments to row constructors, or can implicitly cast to IMat's when they match an operator or function argument.

A visually cleaner way to use ranges is with Scala's -> operator. If you know Scala, you may know that a->b actually evaluates to a 2-tuple of a,b. However BIDmat has implicit rules to cast 2-tuples of numbers to ranges, and from there they can be used to define matrices:

scala> (0->4)on(3->7)
res5: BIDMat.IMat =
   0   1   2   3
   3   4   5   6

Note that the -> operator creates open ranges, i.e. the upper limit is not part of the sequence (like the "until" operator). This simplifies typical indexing expressions in Scala's zero-based matrix indexing regimen.

There are similar column operators col, icol and dcol, e.g.

scala> col(0 to 3)
res4: BIDMat.FMat =
   0
   1
   2 
   3
Or you can produce columns from rows using the transpose method .t :
scala> (0 to 3).t
res4: BIDMat.IMat =
   0 
   1   
   2   
   3

Matrix-Creating Functions

There are several matrix-generating functions: ones, zeros, rand, sprand. Most functions generate FMat results. For random numbers:

scala> val a = rand(4,4)
a: BIDMat.FMat =
   0.46293  0.097704   0.71535   0.10383
   0.12652   0.71330   0.35587   0.72320
   0.45308   0.45136  0.069531   0.39674
   0.73676   0.14168   0.91742   0.64497
and:
scala> rand(3,3) \ ones(3,2) \ zeros(3,4)
res23: BIDMat.FMat =
  0.99463  0.15353  0.78576        1        1        0        0        0        0
  0.50341  0.73873  0.40323        1        1        0        0        0        0
  0.74681  0.89536  0.34410        1        1        0        0        0        0
There are also integer and double precision floating point versions iones, izeros, dones, dzeros and drand.

Row and Col Functions

Its often convenient to define a row or column with given elements, or even a 1x1 matrix with a given value. There are several functions for this purpose. row and col work on FMat data:

> row(1,2,3,4)
res2: BIDMat.FMat = 1,2,3,4
> col(2,2,1)
res3: BIDMat.FMat =
   2
   2
   1

There are also integer irow, icol and double versions drow, dcol.

Loading and Saving

BIDMat can read 2D tabular numerical data from text files, one row per line, fixed number of elements per line. It returns a matrix whose number of rows is the number of lines in the file, and whose number of columns is the number of elements per line. The elements on each line can be separated by spaces, tabs or commas. The loader function also loads binary files, and it needs a hint that the file contains ASCII data. You do this by using a ".txt" extension in the filename. So

> val a = loadFMat("/data/mydata.fmat.txt")

loads a into an FMat of appropriate size. The filename should end with ".txt" but does not have to end with ".fmat.txt". But we recommend using that format to make clear what each file contains. Similarly, you can save data in a matrix a out in ASCII format with

> saveFMat("/data/newdata.fmat.txt", a)

BIDMat can read and save files in Matlab's HDF5 format (set with the "-v7.3" option when saving in Matlab). To load a matlab file, do:

> val a:DMat = load("/data/matdata.mat", "mat1")

where the first argument is the filename and the second is the name of the variable to load. You should use the type qualifier :DMat on the return value to make sure a gets the correct type. Matrices in Matlab have double precision contents by default, but you can specify (and load into BIDMat) single-precision, integer, and cellstring arrays. To save a matrix in Matlab-compatible format, do:



> saveAs("/data/matdata.mat", a, "mat2")

Finally, BIDMat has an lz4-compressed format which is generally much faster than these first two options. To load and lz4-compressed file, do

> val a = loadFMat("/data/mydata.fmat.lz4")

and to save do

> saveFMat("/data/newdata.fmat.lz4")

See here for more details.

Creating Test Matrices

Random Dense Matrices

You can create random dense matrices with either:

> val a=rand(n,m)    //Creating a random FMat
> val a=grand(n,m)   //Creating a random GMat
You may want to use the GMat version if you're in a hurry. Its about 30x faster on our test machine.

Random Labels

To create an array of random binary labels with probability p, just generate a uniform (0,1) array as above, and then do:

> val labels = a < p

which will create an array with the same type as a (assumed to be FMat or GMat) containing 0s and 1s. If you need integer labels apply IMat or GIMat to the output, e.g.

> val labels = IMat(a < p)

To generate random integers in some range 0..k-1, you can do

> val randints = IMat(floor(k * 0.99999f * rand(n,m)))
where the 0.99999f is to guard against the rare event that rand returns 1f.

Random Sparse Matrices

You can create random sparse matrices with the sprand function. It takes two dimension arguments and then a density argument.

val a=sprand(n,m,0.2)   //Creating a random SMat with 20% non-zero values
This returns a sparse matrix with approximately 20% non-zeros and where the non-zero values are distributed uniformly in the range (0,1).

Random Power-Law Matrices

Since power-law matrices are so common in data analysis, there are convenience functions for generating random power-law matrices. The simplest is powrand which takes row,column dimension arguments and then a density argument which indicates the expected number of items per column:

> val a = powrand(10000,1000,100)
> mean(sum(a))
99.5
Which could represent a set of documents over a dictionary of 10000 terms, with 1000 documents of average length 100.

This function generates data by columns. For each column, it samples from a power-law distribution with exponent -1 to determine the number of elements in that column. Using this number, it repeatedly samples from a second power-law distribution with exponent -1 to obtain the final coefficient samples.

powrand uses the general form of sprand, which is:

sprand(nrows, ncols, rowGenFunction, colGenFunction)
This routine samples from the second function (colGenFunction) to get the number of elements in each column and uses this as a count to repeatedly sample from the row generator. For basic power-law data, both functions are specializations of
simplePowerLaw(range:Int)
which produces power-law distributed integers with exponent -1 in the range 0,...,range-1.

For more general power-law data, you can specialize the Pareto random generator whose interface is:

paretoGen(low:Int, high:Int, alpha:Double)
where low,high give the range of output values, and alpha is the shape parameter.