Module Sarek_cuda.Cuda_api

Constants

val max_device_name_length : int

Maximum device name length in characters

val max_ptx_header_preview : int

Maximum PTX header preview length for error messages

Exceptions

exception Cuda_error of Cuda_types.cu_result * string

Deprecated: no longer raised. check below now raises the canonical Cuda_error.Cuda_error (a Backend_error alias) via the shared Sarek_backend_error.Backend_error.Make.check funnel, so every handler in the codebase can catch one exception shape across backends. This declaration is kept only so that out-of-tree code matching on Cuda_api.Cuda_error (code, ctx) still compiles (this library is opam-published).

  • deprecated no longer raised; Cuda_api.check now raises Cuda_error.Cuda_error (Backend_error) - catch that instead
val check : string -> Cuda_types.cu_result -> unit

Check CUDA result and raise a canonical Backend_error on failure.

type kernargs = {
  1. params : unit Ctypes.ptr Ctypes.ptr;
  2. slots : int64 Ctypes.ptr;
}

One launch's argument blocks: slots holds the argument values, one 8-byte slot each, and params the array of pointers into it that cuLaunchKernel receives. Both are ctypes allocations owned by the GC, so they must stay reachable (see retain_kernargs) until the launch has been consumed.

val pending_kernargs : (int * nativeint * kernargs) list Stdlib.ref

Host-side kernel-argument buffers (the array of argument pointers plus the value slots) for launches that may still be in flight, keyed by device id.

cuLaunchKernel is asynchronous. The NVIDIA driver snapshots the kernel parameters synchronously before returning, so a caller may free them right after the call — but the ZLUDA/HIP stack reads them later, at dispatch time. Freeing them when Kernel.launch returns is therefore a use-after-free once the OCaml GC reclaims the blocks: the GPU then reads recycled host memory as the kernel's arguments (observed as an intermittent GPU page fault at a host-range address on ZLUDA).

We retain the buffers here until the stream that ran the launch is drained. Each entry is keyed by (device_id, stream_key) where stream_key is the raw address of the stream handle (0 for the default/null stream, which is what SPOC uses). The buffers are held purely for liveness and are never inspected. A mutex guards the list so launches/retires from different OCaml domains cannot lose an entry (drop = use-after-free) or a removal.

val pending_lock : Stdlib.Mutex.t
val retain_kernargs : int -> nativeint -> kernargs -> unit
val retire_stream : int -> nativeint -> unit

Release buffers for one stream — use after draining exactly that stream (its synchronize, or a blocking op known to run on it).

val retire_device : int -> unit

Release all buffers for a device — use only after a full-context synchronize (drains every stream) or context destruction.

val pending_kernarg_count : int -> nativeint -> int

TEST INSTRUMENTATION: number of launches whose argument blocks are still retained for (device_id, stream_key).

val stream_key_of_ptr : 'a Ctypes.ptr -> nativeint

Raw address of a stream handle; the default (null) stream is 0.

val default_stream_key : nativeint

Key of the default/null stream (what SPOC's blocking memcpys run on).

Device Management

module Device : sig ... end

Memory Management

module Memory : sig ... end

Stream Management

module Stream : sig ... end

Event Management

module Event : sig ... end

Ranged / asynchronous copies, pinned memory, stream ordering

module Async : sig ... end

Kernel Management

module Kernel : sig ... end

Utility Functions

val driver_version : unit -> int * int
val is_driver_available : unit -> bool

Driver-only availability: libcuda is loadable and reports at least one device. Sufficient for the PTX backend, which never calls NVRTC — this is what makes SPOC work on non-NVIDIA CUDA implementations such as ZLUDA, which ship the driver API without libnvrtc.

val is_available : unit -> bool
val memory_info : Device.t -> int64 * int64