Module Rig_amd_amdgpu
The path that opens AMD GPUs through Linux's amdgpu kernel driver.
The kernel driver owns the GPU and shares it with other programs. This library opens a GPU through the driver's compute interface, /dev/kfd, and the GPU's render node, and gives Rig_amd what the device needs (Rig_amd.path): memory the kernel driver allocates and maps, queues it schedules, and events it signals on the GPU's interrupts. The device then runs as every Rig_amd device does. The kernel driver keeps the GPU's firmware, power and resets, and reports faults.
A program opens each of the machine's AMD GPUs through its opener:
let devices = Result.map (fun n -> List.init n (fun i -> Rig.open_ (Rig_amd_amdgpu.gpu i))) (Rig_amd_amdgpu.count ())
Numbering. GPU i is the ith of the machine's AMD GPUs in bus order (Rig_amd.is_gpu), whichever driver holds each, so a GPU taken from the kernel driver keeps its index, and the others theirs. The GPUs are named "AMD", "AMD:1", "AMD:2", ….
The machine's files. buses, count and an opener's make read the machine's files: its PCI functions under /sys/bus/pci, the kernel driver's description of its GPUs under /sys, and the kernel driver's files under /dev.
Privileges. Opening needs read and write access to /dev/kfd and to the GPU's render node, /dev/dri/renderD*, which members of the render group have, and no other privilege.
val buses : unit -> (string list, string) Stdlib.resultbuses () is the bus addresses of the machine's AMD GPUs (Rig_amd.is_gpu), in bus order: by domain, bus, device and function, each as a number. GPU i is the ith, whichever driver holds it. It is Ok [] where /sys/bus/pci does not exist, such as off Linux. Each call reads the files again, and changes nothing on the machine.
A function whose files cannot be read, as while the kernel removes it, is left out.
Error why if the machine's PCI functions cannot be listed, such as when the process has no file left.
val count : unit -> (int, string) Stdlib.resultcount () is the number n of buses, so GPUs are numbered 0 to n - 1, or their Error. A machine with no AMD GPU is Ok 0.
val gpu : ?measure:Rig_amd.measure -> int -> Rig_amd.t Rig_edge.openergpu i is the opener of GPU i, named "AMD" for 0 and "AMD:i" otherwise. Each make () opens a new device of the GPU with queues of its own, also while other devices of the GPU are open, but for one opened with measure. With measure, the opener is named as without, followed by " measuring " and the counters, "SQ_WAVES" first and the others in their order, separated by commas, such as "AMD measuring SQ_WAVES,SQ_INSTS_VALU", and its device measures counters (Measures). While it is open no other device of its GPU opens in the process, an open with other counters included, and it opens only if none is open. Its device holds the GPU's stable power state (The stable power state). The process reads the list of GPUs at the first make that can read it, and keeps it, so after a GPU is plugged in or removed it may differ from buses.
make () returns Error msg:
- if the machine's PCI functions cannot be read, saying why;
- if
iis past the GPUs listed, saying how many there are; - if the
amdgpudriver does not hold the GPU; - if a file cannot be opened, naming it and the reason;
- if the kernel driver refuses a request of the open, naming the step and the reason;
- if the GPU's work faulted in this process, with the fault (The process's GPU);
- if a device of the GPU is open in the process, and either it or this one measures counters, saying so;
- with
Rig_amd.make's message, or the report of a fault while the device opens.
Raises Invalid_argument if i < 0. make raises it, having opened nothing, from a callback inside a lock (Rig's domains section).
The process's GPU
The process opens /dev/kfd once, at the first open that succeeds in making it, and every later open uses that file. The kernel driver gives a process one address space per GPU, which the first open of the GPU takes and the process keeps until it exits. Every device of the GPU works in it, and a fault of one device's work is reported to every device of the GPU.
An open after a device of the GPU was lost makes new queues in that address space. Once the kernel driver has reported a fault of the GPU's work, though, rig refuses the GPU for the rest of the process, since by default (no_queue_eviction_on_vm_fault of the amdgpu module is 0) the kernel driver schedules none of the process's queues on it again. Every later make of the GPU then returns Error, every Rig_amd.sleep of its devices raises the fault, and only another process can use the GPU. A forked child opens a GPU anew, in an address space of its own, and the parent keeps its devices.
Memory. The kernel driver maps a page of host memory once per GPU, for every device of the GPU the process opens. A region whose pages all lie within a region Rig_amd.map_host gave a device of the same GPU shares that mapping, which lasts until the last region over it is freed, also once those devices have stopped. Rig_amd.map_host refuses pages of which some, but not all, lie within such a region. A device reaches the memory of another GPU's device where the kernel driver's topology lists a link between the two GPUs.
Other functions. Rig_amd.dma exports the buffer object a region lies in as a new dma-buf (Rig_edge.dma.Dmabuf), through KFD. The dma-buf holds the object until its last reference goes, freed or not. The kernel driver serves an importer the bus does not carry to the GPU's BAR from host memory, and moves the object there for the GPU's own work too, so the export of GPU memory is Error where Linux's p2pdma would not carry fn's requests to the GPU (Rig_pci.Machine.forwards), and also, for a process that cannot read the bridges' ACS controls, as one without CAP_SYS_ADMIN, wherever only a common bridge would carry them; its reason names that cause. It is Error too for an fn that names no PCI function of the machine, and below KFD 1.12, Linux 6.4. The device's facts name the GPU's function as bus.
Sleeps. From KFD 1.14, Linux 6.5, a release wakes every Rig_amd.sleep that waits for it. Before, a sleep that looked at the word while another domain's sleep was waiting may miss the release's interrupt, which that one takes, and wait out rig's still interval, 200 ms, before it looks again.
Child processes. On Linux before 6.17, each child that the process starts after its first open of an AMD GPU holds up the process's work on its AMD GPUs for about 100 ms. The child inherits the descriptor of the GPU's render node, which the process keeps open until it exits. However the child is started, it closes that copy at its exec, or at its exit if it does not exec, and the close makes the kernel driver take all the process's queues off the GPUs for that time. A program avoids the stall by starting its children before its first open of a GPU through this path, or by running on Linux 6.17 or later, or on a 6.16 stable release that carries the kernel driver's fix. The stable releases of 6.12 lack it.
Faults. The kernel driver reports a fault of the GPU's work, a page fault or a hardware exception such as a reset, with its address or cause, and Rig_amd.sleep raises it. The kernel driver bounds no work on a KFD queue, so the device's hang_ms is 30 s (Rig_edge.facts), and rig loses the device once its work has made no progress for that long.
Tracing. A device's first thread trace takes the GPU's stable power state (The stable power state), and fails while another process holds it. On a GFX 9 GPU, traces take no power state.
The stable power state
The state holds the GPU's clocks and shader engines steady. A device of the GPU that measures counters takes it at open, and a traced one at its first trace. The process holds it while any device of the GPU that took it is open, and gives it back when the last of them stops, which restores the level the GPU had before.
One process holds the state at a time: another process's open or trace that needs it fails meanwhile. A process that exits without stopping its devices gives the state back shortly after its exit, as the kernel driver frees what the process held, so an open just after may still fail. A kernel driver older than its fix of the state's handling may keep it held by a process that exited, which writing auto to the GPU's power_dpm_force_performance_level, as root, frees.