On this page:
2.2.1 Device resources
device-resources
device-resources?
resources-device
resources-sync!
current-device-resources
with-device-resources
2.2.2 Devices
device-count
2.2.3 The default memory resource
2.2.4 Errors
exn:  fail:  raft
2.2.5 Exiting while jobs run
current-exit-wait
2.2.6 Version and ABI
raft-version
raft-abi
raft-abi?
raft-abi-version
raft-abi-raft
raft-abi-rmm
raft-abi-cccl
raft-abi-cuda-runtime
raft-abi-resource-types
raft-abi-handle-size
9.3

2.2 Resources, devices and errors: raft/core🔗ℹ

 (require raft/core) package: raft

Resources, devices, the exception type, and the native library’s version and ABI tag. raft re-exports all of it; see Resources and the GPU for a tutorial.

2.2.1 Device resources🔗ℹ

procedure

(device-resources [#:device device]) → device-resources?

  device : exact-nonnegative-integer? = 0
Creates new resources on device, with a CUDA stream of their own, not CUDA’s per-thread default stream; library handles are created when first needed. The first resources on a device install the default memory resource (The default memory resource). A device the driver does not report raises exn:fail:raft of kind 'logic.

Examples:
> (define r (device-resources))
> r

#<device-resources device 0>

> (resources-device r)

0

Each call makes independent resources with their own stream:

> (define loader (device-resources))
> (define trainer (device-resources))
> (eq? loader trainer)

#f

> (map resources-device (list loader trainer))

'(0 0)

(device-count) is the first number that is not a device:

> (device-count)

1

> (device-resources #:device (device-count))

device-resources: no device 1 among 1

procedure

(device-resources? v) → boolean?

  v : any/c
Returns #t if v is a resources object, released or not.

Examples:

Accepting either a device number or resources:

> (define (as-resources where)
    (if (device-resources? where)
        where
        (current-device-resources where)))
> (eq? (as-resources 0) (current-device-resources))

#t

> (eq? (as-resources loader) loader)

#t

Released resources still satisfy it:

> (define finished (with-device-resources ([r (device-resources)]) r))
> (device-resources? finished)

#t

procedure

(resources-device resources) → exact-nonnegative-integer?

  resources : device-resources?
Returns the device resources run on, even after release.

Example:

Naming the device in a log line:

> (printf "training on cuda:~a\n" (resources-device trainer))

training on cuda:0

Checking that two resources share a device:

> (define (same-device? a b)
    (= (resources-device a) (resources-device b)))
> (same-device? loader trainer)

#t

> (resources-device finished)

0

procedure

(resources-sync! resources) → void?

  resources : device-resources?
Waits until every operation queued on resources’ stream has finished; DeviceResources.sync(). The wait polls, so other Racket threads keep running, and a break ends it with exn:break. Released resources raise exn:fail:raft of kind 'logic.

Example:

Timing GPU work, which returns once queued:

> (define (milliseconds-on r thunk)
    (define start (current-inexact-milliseconds))
    (thunk)
    (resources-sync! r)
    (- (current-inexact-milliseconds) start))
> (real? (milliseconds-on trainer void))

#t

Waiting in a thread of its own:

> (thread-wait
   (thread (lambda ()
             (resources-sync! trainer)
             (displayln "the trainer's stream has drained"))))

the trainer's stream has drained

> (resources-sync! finished)

resources-sync!: the device resources on device 0 were

released

procedure

(current-device-resources [device]) → device-resources?

  device : exact-nonnegative-integer? = 0
Returns the calling thread’s default resources for device, creating them on first use, or again after they are released. The array constructors and conversions of raft/array use it when given no #:resources. Each Racket thread has its own, so threads never share a stream, or a job worker, by accident.

Examples:

Each thread gets its own:

> (define (worker-resources)
    (define answer (make-channel))
    (thread (lambda () (channel-put answer (current-device-resources))))
    (channel-get answer))
> (define a (worker-resources))
> (define b (worker-resources))
> (list (eq? a b) (eq? a (current-device-resources)))

'(#f #f)

Releasing the default makes the next call create a new one:

> (define before (current-device-resources))
> (with-device-resources ([r before])
    (resources-sync! r))
> before

#<device-resources device 0 released>

> (eq? before (current-device-resources))

#f

syntax

(with-device-resources ([id resources-expr] ...) body ...+)

 
  resources-expr : device-resources?
Binds each id as by let*, evaluates the bodys, and releases every bound resources object when control leaves the form by return, raise or escape. Release is idempotent; re-entering the body by a continuation raises exn:fail:raft of kind 'logic. A device array allocated with the resources keeps the stream and handles alive until it is freed. The native teardown runs on a library thread, so a release, explicit or by the finalizer, returns at once (until the exit decides: see Exiting while jobs run) even when destroying a cuBLAS handle or freeing memory waits for the device.

The finalizer alone is correct: it releases unreachable resources at some collection. The form gives their lifetime a clear timeline, a release at a known point, because resources hold GPU and driver state the collector sees only as an estimate: a CUDA stream, a job worker thread and lazily created cuBLAS, cuSOLVER and cuSPARSE handles. Each resources object charges the collector about 1 MiB until it is released, so the defaults of short-lived threads are collected soon after the threads end; a cuBLAS handle alone holds several MiB of device memory. Scoped, they are released when the body exits:

> (define unscoped (device-resources))
> (define scoped (with-device-resources ([r (device-resources)]) r))
> (list unscoped scoped)

'(#<device-resources device 0> #<device-resources device 0 released>)

Example:

Released even when the body fails:

> (define (run-batch items)
    (with-device-resources ([r (device-resources)])
      (for ([item (in-list items)])
        (when (negative? item)
          (error 'run-batch "bad item ~a" item)))
      (resources-sync! r)
      (length items)))
> (run-batch '(1 2 3))

3

> (run-batch '(1 -2 3))

run-batch: bad item -2

Several bindings, released together:

> (define-values (left right)
    (with-device-resources ([left (device-resources)]
                            [right (device-resources)])
      (values left right)))
> (list left right)

'(#<device-resources device 0 released> #<device-resources device 0 released>)

> (resources-sync! left)

resources-sync!: the device resources on device 0 were

released

2.2.2 Devices🔗ℹ

Returns the number of visible CUDA devices, numbered from 0. With no device or no working driver it raises exn:fail:raft of kind 'cuda.

Example:

Spreading workers round robin:

> (define (device-for-worker i)
    (modulo i (device-count)))
> (map device-for-worker '(0 1 2 3))

'(0 0 0 0)

Resources for every device:

> (define per-device
    (for/list ([d (in-range (device-count))])
      (device-resources #:device d)))
> (map resources-device per-device)

'(0)

2.2.3 The default memory resource🔗ℹ

RMM’s current memory resource is per device and shared by every library in the process. The first resources made on a device replace RMM’s default (cudaMalloc, which synchronizes the device) with its CUDA async memory resource, a stream-ordered cudaMallocAsync pool. Any other resource, installed before or after, is left alone; a plain rmm::mr::cuda_memory_resource set beforehand looks like the default and is replaced. A device without memory-pool support keeps the default.

2.2.4 Errors🔗ℹ

struct

(struct exn:fail:raft exn:fail (kind))

  kind : (or/c 'out-of-memory 'cuda 'logic 'generic)
Raised for every failure in RAFT, RMM, CUDA or the native library. The message starts with the Racket procedure that was called, then the cause. The kind says what failed:

  • 'out-of-memory: an allocation failed;

  • 'cuda: a CUDA call failed, for example because no driver is installed;

  • 'logic: the call was refused before reaching the GPU, for example a device that does not exist or resources already released;

  • 'generic: anything else.

Examples:
> (define missing
    (with-handlers ([exn:fail:raft? values])
      (device-resources #:device (device-count))))
> (exn-message missing)

"device-resources: no device 1 among 1"

> (exn:fail:raft-kind missing)

'logic

Dispatching on the kind:

> (define (describe-failure thunk)
    (with-handlers ([exn:fail:raft?
                     (lambda (e)
                       (case (exn:fail:raft-kind e)
                         [(out-of-memory) 'free-memory-and-retry]
                         [(logic) 'fix-the-call]
                         [else 'report]))])
      (thunk)
      'ok))
> (describe-failure (lambda () (current-device-resources)))

'ok

> (describe-failure (lambda () (device-resources #:device 99)))

'fix-the-call

Recovering the procedure name from the message:

> (with-handlers ([exn:fail:raft?
                   (lambda (e)
                     (match-define (list who _ ...)
                       (string-split (exn-message e) ": "))
                     who)])
    (resources-sync! finished))

"resources-sync!"

2.2.5 Exiting while jobs run🔗ℹ

parameter

(current-exit-wait) → (>=/c 0)

(current-exit-wait seconds) → void?
  seconds : (>=/c 0)
 = 10
How long the process’s exit waits for a job made while the parameter holds seconds. A native binding’s long calls run as jobs on worker threads (define-job), so exit, or the main module returning, can come while one is still running. The exit first waits until each job has returned or has had its own seconds, counted from the start of the exit. If none is still running, the process exits normally. Otherwise it ends at once with the requested exit code, skipping the native libraries’ C atexit handlers and static destructors, which would otherwise run under the job. 0 does not wait, and +inf.0 waits until the job returns.

The value is read when a job is made, on the calling thread, so parameterize covers the jobs made in its body and in threads created there. The exit logs to the 'raft topic: the jobs in flight and the time waited at 'debug, and a skipped teardown, with the jobs and the releases still running, at 'warning. If only a place, and not the main place, instantiates raft, the exit still waits, but native destructors registered after the first job or release may run first. Instantiating it from a parallel thread of the main place counts as the main place. A release still running on the library threads that tear resources and arrays down holds the exit the same way, for up to one second after the exit begins or the last job in flight finishes, whichever is later: up to the largest bound in flight plus one second. The wait covers any release made before the exit decides. The exit decides as soon as no job and no release is running, so a release made after that, such as a place’s teardown that starts once everything else has finished, is not waited for. It runs on its caller and can run while the native static destructors run, or be cut off by the process exit. A job or a release that itself calls the C exit skips the teardown, but the C atexit handlers and static destructors registered after the first job or release run first, on that thread, while other jobs and releases keep running.

Examples:

A thread created inside parameterize keeps the value for its jobs:

> (define seen (make-channel))
> (parameterize ([current-exit-wait +inf.0])
    (thread (lambda () (channel-put seen (current-exit-wait)))))

#<thread>

> (channel-get seen)

+inf.0

A negative or non-real value is refused:

> (current-exit-wait -1)

current-exit-wait: expected a non-negative real number of

seconds, given -1

> (parameterize ([current-exit-wait +nan.0]) 'unreached)

current-exit-wait: expected a non-negative real number of

seconds, given +nan.0

2.2.6 Version and ABI🔗ℹ

procedure

(raft-version) → string?

Returns the RAFT release the native library was built against, as year, month and patch: "26.08.00" is August 2026.

Example:
> (raft-version)

"26.08.00"

Comparing releases numerically:

> (match-define (list year month _)
    (map string->number (string-split (raft-version) ".")))
> (list year month)

'(26 8)

> (>= (+ (* 100 year) month) 2608)

#t

Refusing to start on another release:

> (define (require-raft-release! wanted)
    (unless (string=? (raft-version) wanted)
      (error 'my-pipeline "built for RAFT ~a, but this is RAFT ~a"
             wanted (raft-version))))
> (require-raft-release! "26.08.00")
> (require-raft-release! "26.10.00")

my-pipeline: built for RAFT 26.10.00, but this is RAFT

26.08.00

procedure

(raft-abi) → raft-abi?

Returns the ABI tag of the native library: what a second native library, such as a cuML binding, must match to share RAFT handles and arrays with it. Such a library records the tag it was built against and compares it at load time.

Example:
> (raft-abi)

#<raft-abi raft 26.08.00 rmm 26.08.00 cccl 3.4.3 cuda-runtime 13.2 version 1 resource-types 22 handle-size 32>

In a match pattern, raft-abi takes the tag’s fields by keyword. Every keyword is optional and fields not named are ignored; an unknown keyword is a syntax error.

  abi-pattern = (raft-abi field-pattern ...)
     
  field-pattern = #:version pat
  | #:raft pat
  | #:rmm pat
  | #:cccl pat
  | #:cuda-runtime pat
  | #:resource-types pat
  | #:handle-size pat

> (match-define (raft-abi #:raft raft #:cuda-runtime cuda-runtime) (raft-abi))
> (list raft cuda-runtime)

'("26.08.00" "13.2")

A program tested on one release can say so on another:

> (define (support-status abi)
    (match abi
      [(raft-abi #:raft "26.08.00") 'supported]
      [(raft-abi #:raft other) (list 'untested other)]))
> (support-status (raft-abi))

'supported

> (support-status (struct-copy raft-abi (raft-abi) [raft "26.10.00"]))

'(untested "26.10.00")

Checking a recorded tag: a match, a different RAFT release, and a handle layout change a version check would miss:

> (define (abi-mismatches built-against)
    (for/list ([field (in-list (list raft-abi-version raft-abi-raft raft-abi-rmm
                                     raft-abi-cccl raft-abi-handle-size))]
               [name (in-list '(version raft rmm cccl handle-size))]
               #:unless (equal? (field built-against) (field (raft-abi))))
      name))
> (abi-mismatches (raft-abi))

'()

> (abi-mismatches (struct-copy raft-abi (raft-abi) [raft "26.10.00"]))

'(raft)

> (abi-mismatches (struct-copy raft-abi (raft-abi) [handle-size 40]))

'(handle-size)

A one-line support report:

> (match-let ([(raft-abi #:raft raft #:rmm rmm #:cccl cccl) (raft-abi)])
    (format "raft=~a rmm=~a cccl=~a" raft rmm cccl))

"raft=26.08.00 rmm=26.08.00 cccl=3.4.3"

procedure

(raft-abi? v) → boolean?

  v : any/c

procedure

(raft-abi-version abi) → exact-nonnegative-integer?

  abi : raft-abi?

procedure

(raft-abi-raft abi) → string?

  abi : raft-abi?

procedure

(raft-abi-rmm abi) → string?

  abi : raft-abi?

procedure

(raft-abi-cccl abi) → string?

  abi : raft-abi?

procedure

(raft-abi-cuda-runtime abi) → string?

  abi : raft-abi?

procedure

(raft-abi-resource-types abi) → exact-nonnegative-integer?

  abi : raft-abi?

procedure

(raft-abi-handle-size abi) → exact-positive-integer?

  abi : raft-abi?
The predicate and the fields of an ABI tag, a transparent structure: equal? compares tags field by field, and (struct-copy raft-abi abi [field value] ...) makes a changed copy.

Field

  

Value

version

  

the version of the tag itself; it changes whenever a change to the native interface would break a library built against the previous one

raft

  

the RAFT release, as raft-version returns it

rmm

  

the RMM release, in the same form

cccl

  

the CCCL release, for example "3.4.3"

cuda-runtime

  

the CUDA runtime the native library was compiled with, major.minor

resource-types

  

the number of resource kinds a RAFT handle can hold

handle-size

  

sizeof(raft::handle_t), in bytes

Examples:
> (define abi (raft-abi))
> (raft-abi? abi)

#t

> (raft-abi-version abi)

1

> (raft-abi-cuda-runtime abi)

"13.2"

> (equal? abi (raft-abi))

#t

> (equal? abi (struct-copy raft-abi abi [rmm "26.10.00"]))

#f