github ↗
docs menu

Module Rig_nv

The driver of NVIDIA GPUs.

A device of this library is one NVIDIA GPU. The GPU's resource manager (RM) is the part of NVIDIA's software that makes the GPU's objects, such as its channels, its memory and its address spaces, for its clients. A path is a library that reaches the RM one way and gives this library what it reached, as a path. Rig_nv_nvidia reaches it through NVIDIA's kernel driver, and Rig_nv_pci through the GPU's own system processor, with no kernel driver. Each path's opener calls make.

The device runs its work on two hardware channels, one for compute and one for copies. A channel fetches 64-bit entries from its ring, the GPFIFO, in order, and runs the 32-bit words of the segment each entry names (Rig_nv_abi.Method). rig hands the device a submission through the two C functions of rig_nv.h, rig_nv_room and rig_nv_submit. rig_nv_submit writes the submission's parts into the channels' rings and wakes the channels through the GPU's doorbell register, with stores to memory alone. Each submission ends with a release that writes its value into the timeline word.

Rig.submit -> rig_nv_room, rig_nv_submit      C, stores only
                  |                |
                  v                v
          compute channel     copy channel    ring entries name words
                  |                |
                  +-- release v ---+
                          |
                          v
                    timeline word             read by Rig_nv.sleep

A device lives from make, which allocates its channels and their memory, until rig calls stop, which frees the channels and asks the path to let go of the GPU. Between them rig submits, waits in sleep, allocates and frees memory, and loads images.

Any domain may call any function, at the same time as others, within the rules rig keeps for every driver (Rig_edge.Driver). A C caller other than rig must call rig_nv_room before each rig_nv_submit, with no other call of either on the device between them. A function that calls the RM returns the RM's refusal of its arguments as its result, None or Error, and raises Fault for any other failure; signaled never raises it, and make returns it as Error. A path's free that fails keeps the memory it was given, and the call that freed raises its Fault, grow also where it would answer Short. A call that fails for another reason reports that failure in the Fault's place. make returns its own Error, and grow raises its own exception. stop raises a free's Fault once it has stopped the device and given back every other memory. The memory an RM object names stays taken while the RM keeps the object, or while the path keeps the object's channel registered. A Fault from the RM's free or the path's unregister passes on as a free's does, but stop drops it and asks the path to stop (Faults and stopping).

The capability's local and the paths' openers, called from a callback inside a lock (Rig's domains section), raise Invalid_argument, having changed nothing (Drivers). Locks nest in this order, and a take out of it raises Invalid_argument, having taken nothing: an image's, which entry and unload hold; a device's local memory lock, which entry and stop hold; and a path's (Lock).

Compiled code finds the record its launch descriptors depend on under Rig_nv_abi.key:

ocaml
let d = Result.get_ok (Rig.open_ (Rig_nv_nvidia.gpu 0)) in
let g = Option.get (Rig.capability d Rig_nv_abi.key) in
Printf.printf "%s, %d SMs\n" (Rig.arch d)
  (g.gpcs * g.tpcs_per_gpc * g.sms_per_tpc)

The C functions states how rig hands a driver work.

Driver

include Rig_edge.Driver
#type t

The type for open devices of the driver.

#type region

The type for memory of a device.

#type image

The type for loaded code.

#exception Fault of string

Fault why reports that the device faulted. Any counted call may raise it.

#val key : t Stdlib.Type.Id.t

key identifies the driver. rig asks map_peer only of two devices with the same key.

#val facts : t -> region Rig_edge.facts

facts d is d's facts.

Memory

#val alloc : t -> Rig_edge.memory -> int -> region option

alloc d m n is n bytes of d's memory m, or None if d has none. n is positive and a multiple of 4, Rig.word, so the region holds a buffer's bytes past its length up to a whole word. Counted.

#val free : t -> region -> unit

free d r gives back r, which is a region alloc made, a mapping map_peer or map_host made, or d's word of the driver's own (Rig_edge.facts). rig frees r once no work of d that uses it can run, and may free it after stop.

#val locate : region -> Rig_edge.location

locate r is where r lies.

#val peer : t -> t -> bool

peer d d' is true iff map_peer d d' maps Device memory of d'. It must return its result from what d and d' learned when they opened, also once either is lost.

#val map_peer : t -> t -> region -> region Rig_edge.mapping

map_peer d d' r maps for d the memory of r, any memory of d'. d's GPU may already map r's memory, through r or another region. Each region must keep the memory mapped until rig frees it. Counted.

#val map_host : t -> int -> int -> region Rig_edge.mapping

map_host d p n maps for d the n bytes of host memory at p. p starts a page and n is positive. rig calls it only where d declares maps_host. The region must map the whole pages that hold the n bytes, so that work reaches through it each word of the allocation's last bytes (Rig's Words section), and they must stay mapped until the region is freed (free). Counted.

#val dma : t -> region -> fn:string -> (Rig_edge.dma, string) Stdlib.result

dma d r ~fn is how the PCI function at the bus address fn of d's machine, such as "0000:41:00.0", reaches all of r's bytes by DMA, or Error why if it cannot, why naming the cause, such as a memory BAR smaller than the GPU's memory. It is Error why, why naming fn, if fn is not a bus address or names no function of the machine. rig calls it only with a region alloc made. Counted.

The driver decides whether fn reaches the memory where it lies, and must return Error rather than move the memory where fn reaches it. It must make the memory visible to fn after each value's work that wrote it, and visible to the device's later work after fn wrote it and a call of rig's submits that work.

For Bus, the driver must keep the memory where it is, out of any other allocation, and keep d's function from any other open, until the process exits, whatever rig frees or stops meanwhile.

Code

#val image : t -> string -> ((region, image) Rig_edge.code, string) Stdlib.result

image d b takes the binary b, or is Error why if d refuses it. Counted.

#val entry : image -> string -> Rig_edge.entry_answer

entry i f is Entry e with what the driver needs to launch i's function f, or Absent if i has no function f. It must make what a launch of f needs, such as a compiled pipeline or f's scratch memory, so that the driver's submit allocates nothing to launch f. A second call for f must make nothing new. The first call may block. Counted.

It is No_memory n, having made nothing, if the device has no memory for the n bytes f's launches need. rig then runs the rounds of reclamation an allocation does, asks again after each, and raises Rig.Out_of_memory after the last.

Raises Invalid_argument with the driver's reason if f needs more than the device offers, where the driver finds that only here.

#val prepare : image -> string -> unit

prepare i f may start making what entry i f makes and return without waiting, so that the device makes the entries of several functions concurrently: it is entry's asynchronous start. It must report nothing, failures included: the next entry i f must wait for what it started and answer as if it made the entry itself. A prepare of an f that has an entry or a started prepare must start nothing, and a prepare that raises must have changed nothing. Doing nothing is a valid prepare, as on a device whose entry makes nothing slow. Counted.

#val unload : t -> image -> unit

unload d i releases what image, prepare and entry made for i. rig calls it once no work of d that runs i can run. The region of a Place is not i's, and rig frees it after. Counted.

Queues

#val grow : t -> Rig_edge.grown

grow d makes what d's queues need to hold the parts of the submission whose room last answered RIG_GROW on the calling thread (The C functions). It changes nothing the C functions read: a later room puts what it made into the queues. Once it has answered Grown, room must not answer RIG_GROW for those parts again, whatever other submissions' growths answer.

It is Short n if d has no memory for n bytes the growth takes. What it made stays for a later room. rig then runs a round of the reclamation an allocation runs, for d's budget and the host's, and asks room again, which answers RIG_GROW anew for parts that still need a growth.

rig calls it on the thread whose room answered RIG_GROW, after that answer and before that thread calls room again, while the C functions may run on other threads for other submissions. A submission asks at most four times, as an allocation tries: after the last, a Short n raises Rig.Out_of_memory with n. A driver whose room never answers RIG_GROW is never asked. Counted.

Timeline

#val signaled : t -> int

signaled d is the value in d's word, read with acquire order. rig calls it only for a word of the driver's own (Rig_edge.facts), also after stop, until it frees that word. Counted until stop. After it, rig drops its faults. rig may call it while it holds a lock of its own, so it must not block or call rig.

#val sleep : t -> seen:int -> still_ms:int -> unit

sleep d ~seen ~still_ms blocks on the device's events until the word differs from seen, and must return, or raise Fault, at the latest after still_ms milliseconds. It must return immediately if the word already differs. It may return earlier, on an event of other work or a timer of the driver's own, since rig reads the word again after every return and sleeps again while it is unmoved. rig may call it while the driver's submit or commit runs. Counted.

An exception other than Fault that escapes sleep loses nothing (Drivers). The driver must record each event it consumes, such as a fault's report, before it consumes it, so that the next call of sleep still raises Fault for it.

Raises Fault once the device has faulted.

Stopping

#val stop : t -> fault:string option -> unit

stop d ~fault stops the lost d and must not wait for its work. rig calls it only at d's close or at an open of its name, with no counted call of d in flight. fault is Some why if rig lost d for a Fault of a counted call or for its hang bound (hang_ms), and None otherwise.

  • The driver must write the last value given to its submit into the word, with release order, also when that submit failed, and only once no work of d runs, since rig then frees the memory that work uses. If no work runs when stop is called, the driver writes it before stop returns. Otherwise the queues' releases write it, or the driver does once that work runs no more.
  • The driver must drop the work d encoded that runs only once committed, such as an open command buffer.
  • stop must release nothing rig made. rig frees each region and unloads each image itself, also after stop.
  • stop must release what the driver made to run work, such as its queues, and must not wait for that work. A word of the driver's own, and what signaled needs to read it, must stay until rig frees the word.
  • Once the word shows the last value given to submit and stop has returned, no write of d or of its work to the word may land, since the word then passes to a later device.

After stop, rig calls free and unload only once the word reads that last value, also after a stop that raised, such as one an asynchronous exception ended before it ended the device's work. It calls them uncounted and drops their faults. They may come after an open of the same hardware has made a new device of this driver, and must touch nothing of the new device.

#val capability : t -> Rig_nv_abi.Gpu.t

capability g is g's GPU as compiled code sees it, which g's facts declare (Rig_nv_abi.declare).

Facts

Fact

Value

arch

The multiprocessors' architecture, such as "sm_89".

budget

The path's budget.

queues

"COMPUTE:0" and "COPY:0" (Submitting).

copy_engine

Some 1, "COPY:0".

completion

Store: the channels' releases, or a submission's last launch, write the word.

waits

stores and hosts.

may_block

false: its C functions only store to memory.

hang_ms

The path's hang_ms.

maps_host

true iff the path maps host memory.

host_addresses

false: the host does not address Device memory.

alignment

4096: each path allocates whole pages of 4 KiB.

cache_bytes

The path's cache_bytes.

capability

capability (Rig_nv_abi.declare).

word

The word make was given, mapped through the path.

bus

The path's bus.

The device maps the 64 KiB of rig's array of words that holds its word through the path (map_host), so that its channels write it. On a path that maps no host memory, or when the word make was given lies outside the array, the word is eight bytes of the device's own Pinned memory, which starts at the given word's value and which rig reads through signaled. That word, in a page of the device's own, goes back when rig frees it (Rig_edge.facts). The device's values start after that value. The word holds, as an unsigned 64-bit integer in the host's byte order, the last value v such that the work of every value up to v has completed. A channel writes it once its work has completed and that work's writes are visible to the system. A read returns the value before a release or the value after it. This assumes the copy engine writes a 64-bit release as one store. The capability's local grows the local memory the compute channel gives kernels (Rig_nv_abi.Gpu.t.local). A growth takes effect at the next submission that uses the compute channel, after its waits and before its parts. After stop, local returns Error. The record also holds the classes and counts the path read of the GPU, and the windows onto shared and local memory the device set on its compute channel.

Submitting

"COMPUTE:0" runs Words and Launch parts, and "COPY:0" runs Words and Copy parts. A part of Words is ring entries that compiled code wrote (Rig_nv_abi.Gpfifo.entry).

The device keeps device order and prefix order (The C functions), and the release of v waits for the work of v to complete, so two submissions never overlap on the GPU. An untimed submission whose parts all run on the compute channel and whose last part is a launch, of a value whose low 32 bits are not 0, has that launch release v as it completes, which follows every launch before it. Such a v has the high 32 bits of v - 1, so the word goes from v - 1 to v even if the launch writes the 64 bits in two stores, which NVIDIA does not rule out. A part starts once the parts before it on its queue have completed, and reads what they wrote. Words run on their queue's channel. A copy runs on either channel and keeps its place in its queue's order, even a copy of zero bytes. On "COPY:0" a copy part of rows runs as one multi-line launch of the copy engine for each 2{^32} - 1 rows, and a copy of one row, or of rows that hold more than 2 GiB or lie 2{^32} bytes or more apart, as one launch of one line for each 2 GiB of each row. A fill writes its byte by the copy engine's remap of a constant, which reads no memory: in components of four bytes from the first multiple of 4 of its address, 2 GiB a launch, and of one byte in the at most three bytes at each end. On sm_80, sm_86, sm_87 and sm_89, a copy or a fill of one row of at least 256 KiB within the device's own Device memory runs instead as a kernel of the driver on the compute channel.

A wait names an aligned 64-bit word that the device's work addresses, and a value w. It holds the submission's work back until the word holds at least w in circular order (Rig_nv_abi.Method.acquire), whoever wrote it.

Each channel's ring holds 262,144 entries when make allocates the channel. A Words part of n words takes 1 + n/2 of them, and none if n is 0; launches and copies take none of their own. The driver's own words go into segments of the channel's segment ring, and each launch's descriptor and constant bank 0 into the compute channel's launch ring. rig_nv_room returns RIG_GROW for parts that exceed the segment ring, the launch ring or a channel's ring. The growth (grow) makes each such segment or launch ring anew, of the least power of two of bytes, at least a page, that holds twice what the parts take, and the next submission that needs it starts on it. A ring a growth made goes back, once a later growth replaced it, at the device's next growth or allocation after the device has reached the work of the submissions that used it. The 1 MiB rings the device opened with stay until it stops.

The RM fixes the entries of a channel's ring when it allocates the channel, so for parts that exceed a channel's ring the growth makes the device's two channels anew, in a new channel group, as make does: each ring holds the least power of two of entries that holds twice what the parts take, at most 2{^31}, and no fewer than the newest channels'. Once a submission needs the new channels, every submission on the device waits until the device has reached every value submitted before it; the next then starts on them, and places there the words a new channel owes before its parts: the engines' binding, the windows and the local memory. The channels they replace go back at the device's next growth or allocation. A growth that the path or the RM lacks memory or another resource for answers Short (Rig_edge.grown), and the device keeps its channels. Where the path's RM frees no object (rm's free is None), the channels a growth replaces stay until the device stops. Each growth at least doubles a channel's ring, from 262,144 entries to at most 2{^31}, so a device keeps at most 26 replaced sets: their rings, which take less than twice the largest of each channel, and the objects the RM made for each set's channels.

The driver holds at most 32,768 values in flight, a number it chooses for its stamp ring and its marks of each channel's work: rig_nv_room returns RIG_LATER while that many values submitted are not reached.

rig_nv_room returns RIG_NEVER for parts that take more than 2{^31} - 1 entries of a channel's ring, the most the ring holds: a channel holds the log2 of its ring's entries in 5 bits on each generation this driver runs. It returns it too for a part the device does not run:

  • a part that is no words, copy or launch;
  • a part on neither queue, a launch on "COPY:0", or a copy of one byte or more on "COMPUTE:0";
  • ring entries of an odd number of words;
  • an after index that is not below the part's own;
  • a launch whose grid or group has an axis of size 0 or exceeds Rig_nv_abi.Qmd.max_size, whose group has more threads than Rig_nv_abi.Launch.max_threads, or whose dynamic shared memory, rounded up to 128 bytes, exceeds Rig_nv_abi.Launch.dynamic_shared.

Launches, whose constant bank 0 entry bounds, copies whose rows hold at most 2 GiB, with pitches and rows below 2{^32}, and fills of at most 2 GiB take no ring entry of their own, and the segment and launch rings grow to hold them. The driver's words for them close into an entry for each 4 MiB of segment, and a segment ring lies below 2{^40}, so their entries stay below the 262,144 a channel's ring holds from its open, and only memory bounds them. Words, and longer copies, take the entries their sizes take.

A launch's constant bank 0 is its kernel's, of at most Rig_nv_abi.Launch.max_bank_bytes: its first KiB holds the driver's parameters, and the kernel's own start where its compiler places them. A launch's bytes past those the kernel reads (Rig.Image.params) are left out.

rig_nv_room reads the word first, so RIG_LATER means that a value the device was given is not yet reached. rig_nv_submit never fails, since it only stores to memory, and returns RIG_COMMITTED, since the channels run work as soon as they fetch it, and rig never asks the device to commit. A submission of no parts writes its value after its waits and every earlier value.

While a profile is taken, each channel a value's work enters writes a stamp of the GPU's timer after the value's waits, and the channel that releases the value writes one before the release (Rig_nv_abi.Method.release_stamp). On COMPUTE a stamp waits for the channel's earlier work to complete. rig_nv_settle, the edge's settle, or a later rig_nv_submit converts the earliest start and the end to the host's clock, as The C functions states for settle. A stamp of a completed value that the work never wrote makes the next rig_nv_settle fail, and so does a failed read of the GPU's timer in it. Either loses the device.

A device measures the timer against the host's clock through the path's clock, and converts a stamp along the line through its first measure, at open, and its latest, once they lie a second apart, and at the timer's nominal rate of a nanosecond a tick before. A measure takes the time of a host clock between two readings of it. A re-measure whose readings lie more than 10 µs apart is dropped, and the measure at open keeps the first of sixteen reads within 10 µs, or the narrowest, which the first re-measure within 10 µs replaces. A time is thus within half its measure's interval, 5 µs once a measure within the bound is taken, plus the line's rate error over the time since the latest measure, at most 62.5 ms. That rate error is at most 10 µs over the measures' distance, 10 ppm, and the timer's own until they are a second apart. Through NVIDIA's kernel driver (Source) the driver takes the readings around its read of the timer's registers, and the device takes one more pair, to map the driver's clock to the host's. With Registers the device reads the registers itself, at open only, and its times drift from the host's clock at the timer's rate error since open.

A launch with dynamic shared memory runs in the multiprocessors' configuration of the most shared memory (Rig_nv_abi.Qmd.t.shared).

Memory

  • Device memory is the GPU's, which the host does not address.
  • Pinned memory is host memory that the GPU reads and writes coherently with the host.
  • Mapped memory is the GPU's, which the host also addresses through the GPU's memory BAR. The host's stores to it reach the GPU before the work of any later submission of the device reads it.

alloc returns None if the GPU or the host lacks the memory, and for Mapped once the BAR has no room, or if it had none for the path's flush word (flush). alloc and map_host raise Invalid_argument for fewer than one byte. The device itself holds Pinned memory for its rings and its word, and 1 MiB for the descriptors and constant banks of its launches, in the BAR where the BAR had room at make and in Pinned memory otherwise.

locate returns the address of a region's first byte in the GPU's virtual address space (Rig_nv_abi.Gpu). Its handle is the same address, and its host address is None for Device memory. Freeing a view (map_peer) leaves the memory it maps and the other views in place. A view must be freed before the region it maps, as rig does (Rig_edge.Driver): freeing that region first may give its memory or its addresses out again while the view still reaches them.

peer is true iff paths of one key opened both devices and this device's path reaches the other GPU's memory (reaches of path), and map_peer then maps it. A view of Mapped memory orders the host's writes through the other GPU's BAR before this device's later work until the other device stops. map_peer of a stopped device's Mapped memory is Never, and is No_room where this device has no entry left for that order. Peer access between GPUs is untested. map_host answers Never if the path maps no host memory, and the path's refusal of the pages otherwise (map_host).

dma is the path's answer (dma). Another function reads what a value's work wrote once the value is reached: a copy's release writes the word once the copy engine's earlier copies are complete (Rig_nv_abi.Method.copy_release), and a compute release once its launches' writes are visible to the system. The device's later work reads what the function wrote once its writes are complete: a copy engine reads the memory itself, and each launch's descriptor invalidates the compute engine's data caches as the launch starts.

Images

An image is a cubin (Rig_nv_abi.Cubin). image returns Place (n, lay) for a cubin that Rig_nv_abi.Cubin.of_string accepts, where n is its Rig_nv_abi.Cubin.size, and Error msg otherwise. lay r relocates the cubin for the address of r. Every relocation must name a symbol the image defines, so CUDA's driver-supplied device functions, such as __assertfail and vprintf, are unavailable: an image that refers to one is refused even if the kernel to launch never calls it. A device opened with Rig_cuda.gpu runs such images.

The first submission on the compute channel after a lay invalidates the compute engine's instruction cache before its parts, since a cubin may load where another one's code was.

entry returns the address of the kernel's first instruction; the image starts there less the kernel's code offset (Rig_nv_abi.Cubin.kernel). Its params are the kernel's params_bytes, and its threads the launch's Rig_nv_abi.Launch.max_threads. It sets the function up for launch (Rig_nv_abi.Launch.make) and grows the compute channel's local memory to serve it (Rig_nv_abi.Gpu.t.local). It answers No_memory n, having made nothing, if the GPU has no memory for the n bytes of that local memory, and raises Invalid_argument with the reason if the function takes more than a launch can, such as a constant bank 0 of more than 64 KiB. unload frees the launch descriptors of the image's functions, and leaves its code region to rig.

Faults and stopping

The RM stops a channel that faults, such as on a read its page tables refuse, and writes the error into the channel's notifier, a page of Pinned memory. The multiprocessors and the MMU report their own faults to the RM, and a path may learn of others (check).

sleep reads the word and the notifiers every millisecond, and lets other domains run while it waits. As it starts to wait it asks the path for its report, and raises Fault if the path has one. Once a notifier holds an error it asks the path and the RM for their reports, and raises Fault. If the path has a report, Fault carries it. Otherwise Fault has a line for each distinct notifier error, by number and name, then one for each fault the multiprocessors or the MMU reported, or the RM's refusal to read them; an MMU line names the faulting address and access.

While no notifier holds an error, a sleep asks only the path. A fault the RM records without writing a notifier, or a refusal of the RM to read the faults, is then not raised by a sleep. A channel the RM stopped so keeps the word still: where the path bounds progress (hang_ms), rig loses the device with "no progress for n ms", without the RM's lines, and where the RM's watchdog runs (hang_ms is None), the wait does not end. Work that completes after such a fault raises nothing.

A wait for work that runs long lasts until the work ends, unless the path bounds progress: rig then loses the device past that bound.

stop ignores its fault. It ends the path's registration of the channels (unregister) and frees them, which makes the RM preempt their work, then asks the path to stop (stop). If the path unregistered and the RM freed the channels, or the path returns `Stopped, the word holds the last submitted value before stop returns, and the memory the device holds itself goes back to the path. Otherwise the word reaches that value when the channels stop or finish, and that memory stays allocated for the life of the process. Regions rig allocated return as Rig_edge.Driver.stop states, in both cases.

Either way the stop frees the C state the device ran work with, its templates and the channels' records, which the channels never read. The edge that reads the word, 80 bytes, stays for the process.

Once the word shows the last value and stop has returned, no work of the device writes it. A channel's release of a value follows its work, and the channels end their work with the last value's release. The stop raises the word itself only once the channels are freed or the path stopped them, or once both channels' notifiers hold an error: the RM stops a channel it reports an error for. A raised word would release the waits of a channel still running, whose own releases would then lower it, so with one channel stopped the word reaches the last value only as the other's work ends.

Paths

A path reaches a GPU's RM, allocates the RM's device, subdevice and virtual address space for the GPU, reads what the device needs, and gives it all to make as a path. make allocates everything else the device holds, its channels and their memory, through the path. What the driver does when a path or the RM refuses a call, such as a stop that answers `Unknown, is untested.

#type params = (char, Stdlib.Bigarray.int8_unsigned_elt, Stdlib.Bigarray.c_layout) Stdlib.Bigarray.Array1.t

The type for the parameters of an RM call: bytes outside the OCaml heap, which the RM reads and writes in place.

#val params : int -> params

params n is n zero bytes.

#val get : params -> (int * int) -> int

get p (at, n) is the field of n bytes at byte at of p, in the host's byte order, as the RM lays out its parameters: unsigned for 1, 2 and 4 bytes, and for 8 the value Int64.to_int gives, its top bit dropped.

Raises Invalid_argument if n is not 1, 2, 4 or 8, or if the field ends past p.

#val set : params -> (int * int) -> int -> unit

set p (at, n) x writes the low n bytes of x to the field of n bytes at byte at of p, as get reads it.

Raises Invalid_argument if n is not 1, 2, 4 or 8, or if the field ends past p.

#type refusal =
  1. #| Lacking of string
    (*

    The RM or the path lacks the memory or another resource the call takes, such as an RM that answered NV_ERR_NO_MEMORY or NV_ERR_INSUFFICIENT_RESOURCES (NVIDIA's nvstatuscodes.h), or a kernel that failed the call with ENOMEM.

    *)
  2. #| Refused of string
    (*

    Any other refusal or failure.

    *)

The type for the RM's refusal of a call, with a message that names the call and the answer.

#type rm = {
  1. #release : int;
    (*

    The release of the RM's interface, such as 615, which sets the layout of parameters that changed between releases.

    *)
  2. #client : int;
    (*

    The client, which the device's objects descend from.

    *)
  3. #alloc : parent:int -> int -> params option -> (int, refusal) Stdlib.result;
    (*

    alloc ~parent cls p is a new object of class cls under parent, made with the parameters p, or the RM's refusal.

    *)
  4. #token : int -> (int, refusal) Stdlib.result;
    (*

    token ch is the work submit token of the channel ch, the word a device stores at the path's doorbell to wake it (NVC36F_CTRL_CMD_GPFIFO_GET_WORK_SUBMIT_TOKEN), or the RM's refusal.

    *)
  5. #schedule : int -> params -> (unit, refusal) Stdlib.result;
    (*

    schedule group p schedules the channels of the channel group group as p, an NVA06C_CTRL_GPFIFO_SCHEDULE_PARAMS of release, says (NVA06C_CTRL_CMD_GPFIFO_SCHEDULE), or is the RM's refusal.

    *)
  6. #boost : int -> (unit, refusal) Stdlib.result;
    (*

    boost subdevice raises the clocks of the GPU of subdevice to their maximum, at CUDA's high priority, with no end (NV2080_CTRL_CMD_PERF_BOOST with the flags _CMD_BOOST_TO_MAX, _CUDA_YES and _CUDA_PRIORITY_HIGH, for NV2080_CTRL_PERF_BOOST_DURATION_INFINITE), or is the RM's refusal.

    *)
  7. #sm_errors : int -> params -> (unit, refusal) Stdlib.result;
    (*

    sm_errors debugger p reads into p, an NV83DE_CTRL_DEBUG_READ_ALL_SM_ERROR_STATES_PARAMS, the errors of the multiprocessors p asks for, through the debugger object debugger (NV83DE_CTRL_CMD_DEBUG_READ_ALL_SM_ERROR_STATES), or is the RM's refusal.

    *)
  8. #mmu_faults : int -> params -> (unit, refusal) Stdlib.result;
    (*

    mmu_faults debugger p reads into p, an NV83DE_CTRL_DEBUG_READ_MMU_FAULT_INFO_PARAMS, the MMU's faults the debugger object debugger recorded (NV83DE_CTRL_CMD_DEBUG_READ_MMU_FAULT_INFO), or is the RM's refusal.

    *)
  9. #free : (parent:int -> int -> (unit, refusal) Stdlib.result) option;
    (*

    Some f if the RM frees objects before it stops, where f ~parent obj frees obj and every object under it, or is the RM's refusal; None if every object lives as long as the RM, so that a device keeps the channels a growth replaces until it stops (Submitting).

    *)
}

The type for a GPU's resource manager, as a path reaches it: its allocations, frees, and each command the driver sends, which the path answers as NVIDIA's RM does. Any domain may call its functions at any time.

#type gpu = {
  1. #channel_class : int;
    (*

    The class of its channels.

    *)
  2. #compute_class : int;
    (*

    The class of its compute engine.

    *)
  3. #copy_class : int;
    (*

    The class of its copy engine.

    *)
  4. #sm_version : int;
    (*

    The version of its multiprocessors, as the RM reports it (NV2080_CTRL_GR_INFO_INDEX_SM_VERSION), such as 0x809.

    *)
  5. #gpcs : int;
    (*

    Its graphics processing clusters.

    *)
  6. #tpcs_per_gpc : int;
    (*

    The texture processing clusters of one.

    *)
  7. #sms_per_tpc : int;
    (*

    The streaming multiprocessors of one of those.

    *)
  8. #warps_per_sm : int;
    (*

    The most warps a multiprocessor runs concurrently.

    *)
}

The type for what a path reads of a GPU, from which make builds its Rig_nv_abi.Gpu.t.

#type 'm memory = {
  1. #address : int;
    (*

    The GPU address of its first byte. No byte of it lies from windows to 2{^40}. Pinned and Mapped memory, and host memory the path maps, lie below windows, since the device's rings, descriptors and semaphores lie in such memory (Rig_nv_abi.Gpu). A path may map host memory at a GPU address of its own, as the host's may lie higher.

    *)
  2. #host : int option;
    (*

    The host address of its first byte, if the host addresses it.

    *)
  3. #handle : int;
    (*

    The RM's name for it, which channel allocations take.

    *)
  4. #data : 'm;
    (*

    The path's own data for it.

    *)
}

The type for memory a path gives a device.

#type clock =
  1. #| Source of int
    (*

    The address of the path's struct rig_nv_clock (rig_nv.h), which the path keeps while a device may read it. A device reads it at open and again in rig_nv_settle, at most every 62.5 ms.

    *)
  2. #| Registers of int
    (*

    The host address at which the GPU's BAR 0 is mapped, whose timer registers (NV_PTIMER_TIME_0 and _1) a device reads at open only.

    *)

The type for how a device reads the GPU's timer, the clock of its stamps, against the host's clock (Submitting).

#type 'm path = {
  1. #key : 'm Stdlib.Type.Id.t;
    (*

    The path's key. Devices whose paths have one key are of one machine, and map each other's memory with map_peer.

    *)
  2. #index : int;
    (*

    The GPU's number (is_gpu).

    *)
  3. #rm : rm;
    (*

    The GPU's RM.

    *)
  4. #device : int;
    (*

    The RM's device object of the GPU.

    *)
  5. #subdevice : int;
    (*

    Its subdevice object.

    *)
  6. #vaspace : int;
    (*

    The virtual address space the device's channels use.

    *)
  7. #gpu : gpu;
    (*

    The GPU's classes and counts.

    *)
  8. #budget : int;
    (*

    The GPU's memory its work may allocate, in bytes.

    *)
  9. #cache_bytes : int option;
    (*

    Some n with n the bytes of the GPU's L2 cache, its last level, as the RM states it, or None where the RM states none.

    *)
  10. #doorbell : int;
    (*

    The host address of the 4-byte register into which the device stores a channel's work submit token to wake the channel (NVC361_NOTIFY_CHANNEL_PENDING).

    *)
  11. #flush : int option;
    (*

    Some a if the host address a is a 32-bit word of the GPU's Mapped memory, which a device reads before waking its channels so that the host's writes through the GPU's BAR reach the GPU first; None if the BAR had no room for it, and the device then has no Mapped memory. The word must stay mapped until stop is called. Devices that view the GPU's Mapped memory (map_peer) read it too, until stop, which waits for their reads before it calls stop.

    *)
  12. #alloc : Rig_edge.memory -> int -> 'm memory option;
    (*

    alloc k n is n new bytes of the kind k (Memory) at an address aligned to 4 KiB, Mapped memory mapped uncached for the host, or None if the GPU or the host lacks the memory.

    *)
  13. #map_host : (int -> int -> 'm memory option Rig_edge.mapping) option;
    (*

    Some map if the GPU maps host memory: map a n maps the n bytes of host memory at a for the GPU and must answer Maps (Some m), or the path's refusal, No_room where it lacked the room it maps them in and Never otherwise (Rig_edge.mapping). The path maps whole pages and keeps them mapped until every memory it gave over them is freed. None if the GPU maps no host memory.

    *)
  14. #reaches : int -> bool;
    (*

    reaches i is true iff this GPU's work addresses the GPU memory of another GPU of this path's machine, GPU i, numbered as index.

    *)
  15. #map_peer : 'm memory -> 'm memory option Rig_edge.mapping;
    (*

    map_peer m maps for this GPU the memory m of another device of this path and must answer Maps (Some _), or its refusal: Never where this GPU cannot address it, No_room where it lacked the room to map it.

    *)
  16. #free : 'm memory -> unit;
    (*

    free m gives back what alloc, map_host or map_peer gave.

    *)
  17. #bus : string option;
    (*

    The bus address of the GPU's PCI function on its machine.

    *)
  18. #dma : 'm memory -> fn:string -> (Rig_edge.dma, string) Stdlib.result;
    (*

    dma m ~fn is how the function at fn reaches the memory m that alloc gave, as Rig_edge.Driver.dma states, or Error with the path's reason.

    *)
  19. #register : int -> (unit, string) Stdlib.result;
    (*

    register c makes the channel c, which the device allocated under device, ready to run, as the path requires.

    *)
  20. #unregister : int -> (unit, string) Stdlib.result;
    (*

    unregister c gives back what register c took, before the device frees c.

    *)
  21. #check : unit -> unit;
    (*

    check () raises Fault with the path's report of a fault of the GPU's work that the RM sent the path, or of the GPU's failure, such as a lost function. It must not wait for another process: sleep calls it as each wait starts, so a sleep lasts past its still_ms by as long as check takes. A PCI path's check reads the GPU's configuration space, a round trip where the GPU is on another machine.

    *)
  22. #hang_ms : int option;
    (*

    Some n if work whose timeline word has not moved for n milliseconds hung, where nothing else bounds the GPU's work; None where the path's kernel driver bounds it. It is the hang_ms of the device's facts (Rig_edge.facts).

    *)
  23. #clock : clock;
    (*

    How a device reads the GPU's timer against the host's clock.

    *)
  24. #stop : unit -> [ `Stopped | `Unknown ];
    (*

    stop () ends the path's hold of the GPU after the device has freed its channels. It must return `Stopped only once none of the device's work runs or can write memory outside the GPU, and `Unknown if some may. It takes no fault: no path keeps GPU state that a fault spoils for later opens, since NVIDIA's kernel driver recovers its GPUs itself and a GPU held over PCI is reset by its next open whatever ended its last.

    *)
}

The type for what a path gives a device. Each function must return the RM's refusal as its result and raise Fault for any other failure, and must allow any domain to call it at the same time as others. It takes its locks, and its opener checks the thread's, as A path's locks states. After the device's stop, the device calls only free and reaches. It keeps free until it has given back all the memory the path gave, and reaches for the life of the process.

A path's locks

For a path's implementation. A path must take each lock it holds across OCaml code with Lock.protect, which counts it among the locks its thread holds, so that a call from a callback inside it is refused as it starts (Drivers). A path's lock comes last in the order of the module's preamble, and a path holds one of its locks at a time (Lock.protect).

Warning. A path's opener's make and its public functions that may take a lock must run their whole body inside Lock.guarded; one that does not may raise Invalid_argument from a lock it takes later, with what it changed before left changed, and a signal's handler may run anywhere in it.

#module Lock : sig ... end
#val make : word:nativeint -> 'm path -> (t, string) Stdlib.result

make ~word p is the device of the GPU p reaches, whose timeline word is the 8 bytes at the host address word (Rig_edge.opener). Under p.device it allocates a channel group with one context share, the two channels, whose rings Submitting sizes, and their memory, and asks the RM to run the GPU at its highest clocks.

It then takes its first measure of the GPU's timer through p.clock.

Error msg if p.rm.release is none of 570, 580, 610 and 615, if the RM refuses an object, if p lacks the memory, or if p.clock cannot read the timer. A failed make frees what it allocated, but an object the RM does not free, or a registration p does not end, and the memory either names, which the GPU may still read: msg then says what stays, after its reason, and a fault of that give-back. p's own objects stay allocated, and another make may use them once this one has failed or its device has stopped.

#val windows : int

windows is 2{^40} - 8 GiB, the first of the GPU addresses at which a device's kernels see their local and shared memory, which end at 2{^40}. A path gives no memory there (memory).

#val is_gpu : vendor:int -> class_:int -> bool

is_gpu ~vendor ~class_ is true iff a PCI function of vendor vendor and 24-bit class code class_ is an NVIDIA GPU: NVIDIA's vendor 0x10de and a display controller, base class 0x03.

Every path numbers a machine's NVIDIA GPUs 0, 1, … in bus order among such functions, whichever kernel driver holds each: GPU i of one path is GPU i of every other.