2.4 Conversions: raft/compat
| (require raft/compat) | package: raft |
Conversions between device arrays and Racket data: the list and flvector conversions of raft/array, plus vectors, f32vectors, f64vectors, byte strings, and math/matrix and math/array values. raft does not re-export it, since it requires math-lib. See also Other forms of Racket data.
Racket value |
| To the GPU |
| Back to Racket |
list |
|
| ||
nested lists |
|
| ||
vector |
|
| ||
nested vectors |
|
| ||
|
| |||
|
| |||
byte string |
|
| ||
math/matrix matrix |
|
| ||
row or column matrix |
|
| ||
math/array array, rank 1 or 2 |
|
|
The rules are those of Converting Racket data. Without #:dtype, the element type is inferred: exact integers give 'int64, other reals and empty data 'float64; an flvector, f64vector or flonum array gives 'float64, an f32vector 'float32. Exact rationals give 'float64, and booleans and integers from 263 to 264-1 are refused. Integer types truncate toward zero. A value the type cannot hold (a complex number, an infinity or NaN for an integer type, an out-of-range number) and a ragged row raise exn:fail:raft. #:layout sets the packing order on the host. Coming back, a conversion waits for the work queued on the array’s stream; floating-point elements become flonums, integers exact integers.
Rank. Device arrays have one or two axes; any other inferred rank raises exn:fail:raft.
Speed. An flvector, flonum array or f64vector sent as 'float64, an f32vector sent as 'float32, and a byte string are copied as they are; everything else is packed element by element.
2.4.1 Vectors and nested data
procedure
(vector->device-vector xs [ #:dtype dtype #:resources resources]) → device-vector? xs : (vectorof real?) dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f resources : device-resources? = (current-device-resources)
> (vector->device-vector #(3 1 4 1 5)) #<device-vector int64[5] cuda:0 [3 1 4 1 5]>
> (vector->device-vector #(0.5 1.5 2.5) #:dtype 'float32) #<device-vector float32[3] cuda:0 [0.5 1.5 2.5]>
Weights in the type cuML takes:
> (define weights (for/vector ([i (in-range 6)]) (/ 1.0 (add1 i)))) > (vector->device-vector weights #:dtype 'float32) #<device-vector float32[6] cuda:0 [ 1.0 0.5 0.33333334 0.25 0.2 0.16666667]>
One column of a table of vector rows:
> (define flowers (vector #("setosa" 5.1 3.5) #("versicolor" 7.0 3.2) #("virginica" 6.3 3.3))) > (define lengths (vector-map (lambda (row) (vector-ref row 1)) flowers)) > (device-vector->list (vector->device-vector lengths)) '(5.1 7.0 6.3)
A string is refused:
> (vector->device-vector (vector-map (lambda (row) (vector-ref row 0)) flowers)) vector->device-vector: cannot infer a dtype: "setosa" is not
a real number
procedure
(device-vector->vector v) → (vectorof real?)
v : device-vector?
> (device-vector->vector (vector->device-vector #(2 7 1 8))) '#(2 7 1 8)
Cluster sizes from labels:
> (define labels (vector->device-vector #(0 2 1 2 2 0) #:dtype 'int32)) > (define counts (make-vector 3 0))
> (for ([label (in-vector (device-vector->vector labels))]) (vector-set! counts label (add1 (vector-ref counts label)))) > counts '#(2 1 3)
The best score and its index:
> (define scores (device-vector->vector (vector->device-vector #(0.2 0.9 0.4)))) > (define best (vector-argmax values scores)) > (list best (vector-member best scores)) '(0.9 1)
procedure
(vector*->device-matrix rows [ #:dtype dtype #:layout layout #:resources resources]) → device-matrix? rows : (vectorof (vectorof real?)) dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f layout : (or/c 'row-major 'col-major) = 'row-major resources : device-resources? = (current-device-resources)
> (vector*->device-matrix #(#(1 2 3) #(4 5 6)))
#<device-matrix int64[2×3] row-major cuda:0
[[1 2 3]
[4 5 6]]>
A grid laid out by column:
> (define grid (for/vector ([x (in-range 3)]) (for/vector ([y (in-range 2)]) (+ (* 0.5 x) y)))) > (define G (vector*->device-matrix grid #:dtype 'float32 #:layout 'col-major)) > (list (shape G) (contiguous? G #:layout 'col-major) (strides G)) '((3 2) #t (1 3))
A ragged row is refused:
> (vector*->device-matrix #(#(1.0 2.0) #(3.0))) vector*->device-matrix: row 1 has 1 element, but row 0 has
2: '#(3.0)
procedure
(device-matrix->vector* m) → (vectorof (vectorof real?))
m : device-matrix?
> (device-matrix->vector* (vector*->device-matrix #(#(1 2) #(3 4)))) '#(#(1 2) #(3 4))
One centroid of several:
> (define centroids (vector*->device-matrix #(#(5.0 3.4) #(6.6 3.0) #(5.9 2.8)))) > (vector-ref (device-matrix->vector* centroids) 1) '#(6.6 3.0)
A column-major matrix still comes back as rows:
> (device-matrix->vector* G) '#(#(0.0 1.0) #(0.5 1.5) #(1.0 2.0))
procedure
(list*->device-array xs [ #:dtype dtype #:layout layout #:resources resources]) → device-array?
xs :
(or/c (listof real?) (listof (or/c (listof real?) (vectorof real?)))) dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f layout : (or/c 'row-major 'col-major) = 'row-major resources : device-resources? = (current-device-resources)
> (list*->device-array '(1.0 2.0 3.0)) #<device-vector float64[3] cuda:0 [1.0 2.0 3.0]>
> (list*->device-array '((1 2) (3 4)))
#<device-matrix int64[2×2] row-major cuda:0
[[1 2]
[3 4]]>
One call for a series or a table:
> (define (upload data) (list*->device-array data #:dtype 'float32)) > (map shape (list (upload '(0.5 1.5)) (upload '((0.5 1.5) (2.5 3.5) (4.5 5.5))))) '((2) (3 2))
> (shape (list*->device-array '())) '(0)
Other nestings are refused:
> (list*->device-array '(((1 2) (3 4)))) list*->device-array: rank 3 is not supported yet; rank 1 and
2 convert, and any rank arrives in leg 3
> (list*->device-array '(1.0 (2.0 3.0))) list*->device-array: element 1 is a list, but element 0 is
not: '(2.0 3.0)
procedure
(vector*->device-array xs [ #:dtype dtype #:layout layout #:resources resources]) → device-array?
xs :
(or/c (vectorof real?) (vectorof (or/c (vectorof real?) (listof real?)))) dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f layout : (or/c 'row-major 'col-major) = 'row-major resources : device-resources? = (current-device-resources)
> (vector*->device-array #(1 2 3)) #<device-vector int64[3] cuda:0 [1 2 3]>
> (vector*->device-array #(#(1 2) #(3 4)))
#<device-matrix int64[2×2] row-major cuda:0
[[1 2]
[3 4]]>
Data read back from a Racket literal:
> (define saved (read (open-input-string "#(#(0.1 0.2) #(0.3 0.4) #(0.5 0.6))"))) > (shape (vector*->device-array saved #:dtype 'float32)) '(3 2)
A non-row element is refused:
> (vector*->device-array (vector #(1 2) 3)) vector*->device-array: row 1 is not a list or vector, but
row 0 is: 3
procedure
→ (or/c (listof real?) (listof (listof real?))) a : device-array?
> (device-array->list* (list*->device-array '(1 2 3))) '(1 2 3)
> (device-array->list* (list*->device-array '((1 2) (3 4)) #:layout 'col-major)) '((1 2) (3 4))
Results as JSON, whatever their rank:
> (define (results->json a) (jsexpr->string (hasheq 'shape (shape a) 'values (device-array->list* a)))) > (results->json (list*->device-array '((1 2) (3 4)) #:dtype 'int32)) "{\"shape\":[2,2],\"values\":[[1,2],[3,4]]}"
> (results->json (vector->device-vector #(0.5 0.25))) "{\"shape\":[2],\"values\":[0.5,0.25]}"
A sum over either rank:
> (define (total a) (define xs (device-array->list* a)) (apply + (if (device-matrix? a) (apply append xs) xs))) > (map total (list (list*->device-array '(1 2 3)) (list*->device-array '((1 2) (3 4))))) '(6 10)
procedure
→ (or/c (vectorof real?) (vectorof (vectorof real?))) a : device-array?
> (device-array->vector* (vector*->device-array #(1 2 3))) '#(1 2 3)
> (device-array->vector* (vector*->device-array #(#(1 2) #(3 4)))) '#(#(1 2) #(3 4))
Indexing a result:
> (define table (device-array->vector* centroids)) > (vector-ref (vector-ref table 2) 0) 5.9
Sorting the host copy leaves the device array unchanged:
> (define distances (vector->device-vector #(2.5 0.5 1.5))) > (vector-sort (device-array->vector* distances) <) '#(0.5 1.5 2.5)
> (device-vector->list distances) '(2.5 0.5 1.5)
2.4.2 Packed numbers: f32vectors, f64vectors and bytes
procedure
(f32vector->device-vector xs [ #:dtype dtype #:resources resources]) → device-vector? xs : f32vector? dtype : (or/c 'float32 'float64 'int32 'int64) = 'float32 resources : device-resources? = (current-device-resources)
> (f32vector->device-vector (f32vector 0.5 1.5 2.5)) #<device-vector float32[3] cuda:0 [0.5 1.5 2.5]>
Single-precision samples from an audio decoder:
> (define samples (make-f32vector 4 0.25)) > (f32vector-set! samples 2 -0.75) > (define on-gpu (f32vector->device-vector samples)) > (list (dtype on-gpu) (device-vector->list on-gpu)) '(float32 (0.25 0.25 -0.75 0.25))
Widened, or truncated to integers:
> (device-vector->list (f32vector->device-vector (f32vector 0.1 2.7) #:dtype 'float64)) '(0.10000000149011612 2.700000047683716)
> (device-vector->list (f32vector->device-vector (f32vector 0.1 2.7) #:dtype 'int32)) '(0 2)
procedure
(f64vector->device-vector xs [ #:dtype dtype #:resources resources]) → device-vector? xs : f64vector? dtype : (or/c 'float32 'float64 'int32 'int64) = 'float64 resources : device-resources? = (current-device-resources)
> (f64vector->device-vector (f64vector 1.0 2.0 3.0)) #<device-vector float64[3] cuda:0 [1.0 2.0 3.0]>
Readings a C library filled, narrowed for cuML:
> (define readings (list->f64vector '(21.5 21.75 22.0 22.5))) > (f64vector->device-vector readings #:dtype 'float32) #<device-vector float32[4] cuda:0 [ 21.5 21.75 22.0 22.5]>
Doubles sent as integers:
> (dtype (f64vector->device-vector (f64vector 3.0 0.0 12.0) #:dtype 'int64)) 'int64
procedure
v : device-vector?
> (f32vector->list (device-vector->f32vector (f32vector->device-vector (f32vector 0.5 1.5)))) '(0.5 1.5)
For a C library that takes float*:
> (define out (device-vector->f32vector (vector->device-vector #(0.1 0.2)))) > (list (f32vector-length out) (f32vector-ref out 0)) '(2 0.10000000149011612)
Integer labels as floats:
> (f32vector->list (device-vector->f32vector labels)) '(0.0 2.0 1.0 2.0 2.0 0.0)
procedure
v : device-vector?
> (f64vector->list (device-vector->f64vector (f64vector->device-vector readings))) '(21.5 21.75 22.0 22.5)
A 'float32 vector widens:
> (f64vector->list (device-vector->f64vector (vector->device-vector #(0.1) #:dtype 'float32))) '(0.10000000149011612)
Summed on the host:
> (define totals (device-vector->f64vector (vector->device-vector #(1.5 2.5 3.0)))) > (for/sum ([i (in-range (f64vector-length totals))]) (f64vector-ref totals i)) 7.0
procedure
(bytes->device-vector bs #:dtype dtype [ #:resources resources]) → device-vector? bs : bytes? dtype : (or/c 'float32 'float64 'int32 'int64) resources : device-resources? = (current-device-resources)
> (define raw (bytes-append (real->floating-point-bytes 0.5 4) (real->floating-point-bytes 1.5 4))) > (bytes->device-vector raw #:dtype 'float32) #<device-vector float32[2] cuda:0 [0.5 1.5]>
Labels from a binary file:
> (define label-file (make-temporary-file))
> (define label-bytes (apply bytes-append (for/list ([label (in-list '(0 2 1 1))]) (integer->integer-bytes label 4 #t)))) > (display-to-file label-bytes label-file #:exists 'truncate) > (device-vector->list (bytes->device-vector (file->bytes label-file) #:dtype 'int32)) '(0 2 1 1)
A partial element is refused:
> (bytes->device-vector (make-bytes 6) #:dtype 'float32) bytes->device-vector: 6 bytes do not hold a whole number of
4-byte float32 elements
procedure
(device-vector->bytes v) → bytes?
v : device-vector?
> (device-vector->bytes (vector->device-vector #(1 2) #:dtype 'int32)) #"\1\0\0\0\2\0\0\0"
A round trip through a file:
> (define result-file (make-temporary-file))
> (display-to-file (device-vector->bytes (vector->device-vector #(0.5 0.25))) result-file #:exists 'truncate) > (file-size result-file) 16
> (device-vector->list (bytes->device-vector (file->bytes result-file) #:dtype 'float64)) '(0.5 0.25)
Reinterpreted as another element type:
> (define one (device-vector->bytes (vector->device-vector #(1.0) #:dtype 'float32))) > (device-vector->list (bytes->device-vector one #:dtype 'int32)) '(1065353216)
2.4.3 math/matrix and math/array
A flonum array or mutable array hands over its storage in one step; any other array is read through a Typed Racket contract per element. Results come back as flonum arrays (FlArray) for floating-point device arrays and mutable arrays of exact integers for integer ones.
procedure
(matrix->device-matrix m [ #:dtype dtype #:layout layout #:resources resources]) → device-matrix? m : matrix? dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f layout : (or/c 'row-major 'col-major) = 'row-major resources : device-resources? = (current-device-resources)
> (matrix->device-matrix (matrix [[1.0 2.0] [3.0 4.0]]))
#<device-matrix float64[2×2] row-major cuda:0
[[1.0 2.0]
[3.0 4.0]]>
A Gram matrix in single precision:
> (define A (matrix [[1.0 2.0] [3.0 4.0] [5.0 6.0]])) > (define gram (matrix* (matrix-transpose A) A)) > (matrix->device-matrix gram #:dtype 'float32)
#<device-matrix float32[2×2] row-major cuda:0
[[35.0 44.0]
[44.0 56.0]]>
Exact entries infer 'int64; ask for the type you need:
> (dtype (matrix->device-matrix (identity-matrix 3))) 'int64
> (define I (matrix->device-matrix (identity-matrix 3) #:dtype 'float64 #:layout 'col-major)) > (list (dtype I) (contiguous? I #:layout 'col-major)) '(float64 #t)
procedure
(device-matrix->matrix m) → array?
m : device-matrix?
> (device-matrix->matrix (matrix->device-matrix (matrix [[1.0 2.0] [3.0 4.0]]))) (flarray #[#[1.0 2.0] #[3.0 4.0]])
Host algebra on the result:
> (define back (device-matrix->matrix (matrix->device-matrix gram))) > (list (matrix-trace back) (matrix-determinant back)) '(91.0 24.0)
Integer matrices stay exact:
> (define counts-matrix (device-matrix->matrix (list*->device-matrix '((2 1) (1 3)) #:dtype 'int32))) > (matrix-inverse counts-matrix) (array #[#[3/5 -1/5] #[-1/5 2/5]])
procedure
(matrix->device-vector m [ #:dtype dtype #:resources resources]) → device-vector? m : matrix? dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f resources : device-resources? = (current-device-resources)
> (matrix->device-vector (col-matrix [1 2 3])) #<device-vector int64[3] cuda:0 [1 2 3]>
> (matrix->device-vector (row-matrix [0.5 1.5])) #<device-vector float64[2] cuda:0 [0.5 1.5]>
A host-solved linear system:
> (define x (matrix-solve (matrix [[2.0 1.0] [1.0 3.0]]) (col-matrix [3.0 5.0]))) > (matrix->device-vector x #:dtype 'float32) #<device-vector float32[2] cuda:0 [0.8 1.4]>
> (matrix->device-vector gram) matrix->device-vector: a 2×2 matrix is neither a row nor a
column matrix
procedure
v : device-vector?
> (device-vector->col-matrix (vector->device-vector #(1.0 2.0 3.0))) (flarray #[#[1.0] #[2.0] #[3.0]])
Checking a result on the host:
> (define coefficients (vector->device-vector #(1.0 -1.0))) > (matrix* A (device-vector->col-matrix coefficients)) (array #[#[-1.0] #[-1.0] #[-1.0]])
A Euclidean norm:
> (matrix-norm (device-vector->col-matrix (vector->device-vector #(3.0 4.0)))) 5.0
procedure
v : device-vector?
> (device-vector->row-matrix (vector->device-vector #(1 2 3))) (mutable-array #[#[1 2 3]])
Stacking results into one matrix:
> (define runs (list (vector->device-vector #(0.25 0.5)) (vector->device-vector #(0.75 1.0)))) > (matrix-stack (map device-vector->row-matrix runs)) (array #[#[0.25 0.5] #[0.75 1.0]])
A dot product as row times column:
> (define u (vector->device-vector #(1.0 2.0))) > (matrix* (device-vector->row-matrix u) (device-vector->col-matrix u)) (array #[#[5.0]])
procedure
(array->device-array arr [ #:dtype dtype #:layout layout #:resources resources]) → device-array? arr : array? dtype : (or/c #f 'float32 'float64 'int32 'int64) = #f layout : (or/c 'row-major 'col-major) = 'row-major resources : device-resources? = (current-device-resources)
> (array->device-array (flarray #[#[1.0 2.0] #[3.0 4.0]]))
#<device-matrix float64[2×2] row-major cuda:0
[[1.0 2.0]
[3.0 4.0]]>
> (array->device-array (array #[1 2 3])) #<device-vector int64[3] cuda:0 [1 2 3]>
A build-array table, read element by element:
> (define distances-table (build-array #(3 3) (lambda (js) (abs (- (vector-ref js 0) (vector-ref js 1)))))) > (array->device-array distances-table #:dtype 'float32)
#<device-matrix float32[3×3] row-major cuda:0
[[0.0 1.0 2.0]
[1.0 0.0 1.0]
[2.0 1.0 0.0]]>
Columns sliced on the host first:
> (define measurements (flarray #[#[5.1 3.5 1.4] #[7.0 3.2 4.7] #[6.3 3.3 6.0]])) > (define first-two (array-slice-ref measurements (list (::) (:: 0 2)))) > (device-array->list* (array->device-array first-two)) '((5.1 3.5) (7.0 3.2) (6.3 3.3))
> (array->device-array (array #[#[#[1 2]]])) array->device-array: rank 3 is not supported yet; rank 1 and
2 convert, and any rank arrives in leg 3
procedure
(device-array->array a) → array?
a : device-array?
> (device-array->array (vector->device-vector #(1.5 2.5))) (flarray #[1.5 2.5])
> (device-array->array (list*->device-matrix '((1 2) (3 4)) #:dtype 'int32)) (mutable-array #[#[1 2] #[3 4]])
Summing a result:
> (array-all-sum (device-array->array (vector->device-vector #(0.5 1.5 2.0)))) 4.0
Host arithmetic, sent back:
> (define scaled (array-scale (device-array->array (vector->device-vector #(1.0 2.0 3.0))) 10.0)) > (array->device-array scaled) #<device-vector float64[3] cuda:0 [10.0 20.0 30.0]>
2.4.4 From raft/array
Re-exported from Converting Racket data: