rig #
rig runs work on GPUs from OCaml, and depends on nothing outside the OCaml distribution.
rig talks to NVIDIA's and AMD's kernel drivers itself, so a program needs
no CUDA or ROCm, and on Linux it opens AMD and NVIDIA GPUs with no kernel
driver. A program allocates memory on a device and submits work that uses
it. rig orders that work by the memory it reads and writes, so a program
never names an event or a fence. Another machine's GPUs open through
rig agent running there, and take the same calls as this machine's.
A first program #
This program copies five bytes to a memory device, a device whose memory is the host's and whose work runs before the call that submits it returns, and back again. It runs on any machine:
# open Rig;;
# let () =
let d = Result.get_ok (memory_device "M") in
let on_d = Buffer.create d 5 in
Buffer.blit_from_string "hello" 0 on_d 0 5;
let b = Bytes.create 5 in
Buffer.blit_to_bytes on_d 0 b 0 5;
print_endline (Bytes.to_string b);;
hello
The blit back reads on_d, so rig starts it after the first blit has
written it. Whatever reads memory follows every device's last write of
it, and whatever writes memory follows every use (the Order section of
rig.mli).
On a GPU, only the line that opens the device changes. On a Mac:
let d = Result.get_ok (Rig.open_ (Rig_metal.gpu 0))
with rig.metal added to the executable's libraries. The opener is the
only line that names a GPU and the path to it, and the table below lists
them. The guide runs a kernel on a GPU.
Why does rig exist? #
Running a model on a GPU today means installing a vendor's stack: PyTorch for CUDA alone is 5.5 GB, most of it NVIDIA's libraries, and a full ROCm install is over 26 GB, at versions that must agree with each other and with the kernel. None of it fits in one person's head, and all of it must be installed wherever the program runs.
We want raven to own the whole path from a model to the GPU, and to keep
that path small enough to read. rig is its foundation. It does the work of
AMD's runtime, about 300,000 lines, and of NVIDIA's closed libcuda, in
about 37,000 lines of OCaml and C, and on Linux it boots their GPUs with no
kernel driver at all. Its interfaces are written to read like a book. A
reader who starts with rig.mli and follows one driver learns how a GPU
runs their work, from the call that submits it to the moment its memory
comes back.
What does it run on? #
rig has run on three machines: an M1 Max Mac, a Linux PC with an NVIDIA
RTX 5000 Ada (kernel driver 615), and a Linux PC with an AMD Radeon AI PRO
R9700 (gfx1201). No test machine runs Windows. The core, host, pool, file
and CUDA code has paths for Windows that have never run there. A GPU's opener
goes to Rig.open_.
| Device | Open with | Status |
|---|---|---|
| The host's cores | Rig.host |
tested on all three machines |
| Files | Rig_disk.of_file |
tested on all three machines |
| Apple GPUs, through Metal | Rig_metal.gpu 0 |
tested on the M1 Max |
| NVIDIA GPUs, through CUDA | Rig_cuda.gpu 0 |
tested on the RTX 5000 Ada |
| NVIDIA GPUs, through NVIDIA's kernel driver | Rig_nv_nvidia.gpu 0 |
tested on the RTX 5000 Ada with release 615; releases 570, 580 and 610 untested |
| NVIDIA GPUs, with no kernel driver | Rig_nv_pci.gpu 0 |
written, never run on hardware |
AMD GPUs, through amdgpu |
Rig_amd_amdgpu.gpu 0 |
tested on the R9700 |
| AMD GPUs, with no kernel driver | Rig_amd_pci.gpu 0 |
tested on the R9700 |
| AMD GPUs other than gfx1201 | either AMD opener | untested |
| AMD Instinct virtual functions and XGMI | Rig_amd_pci.gpu |
written, never run on hardware |
| Two GPUs of one machine reaching each other's memory | the AMD, NVIDIA and CUDA openers | untested |
| Other machines | Rig_remote.connect, rig run, rig agent |
tested between the RTX 5000 Ada and R9700 machines |
| ConnectX NICs | Rig_mlx5.open_ |
run against Linux's Soft-RoCE only, never a real NIC |
| Broadcom NetXtreme NICs, with no kernel driver | Rig_bnxt.open_ |
written, never run on hardware |
When should you not use rig? #
- You want to write GPU kernels. rig loads code compiled for the device, by nvcc, hipcc, Metal's compiler or raven's compiler, tolk, and runs it.
- You need the GPU for other programs while yours runs. A driver-less open holds the GPU for its process alone.
- You want a GPU without its kernel driver on macOS or Windows. The driver-less paths run on Linux only.
- You wait on each small step of work on a Mac. Each wait pays Metal's round trip, about 100 µs from commit to the start of an idle GPU and about 95 µs from the end of a command buffer to the call that tells the host. Work no one waits for is batched, at about 2.5 µs a submission.
- Your machines share a network with people you do not trust. A job across machines checks a shared key when it connects, and authenticates or encrypts nothing after that.
Where next #
- The guide installs rig, explains each call of a first
program, and runs a kernel on a GPU. Its pages trace the rules at work,
such as one submission from
submitto the hardware and back. - The interfaces, in reading order:
rig.mli, thenrig_edge.mli, the contract a driver implements, then one driver, such asrig_metal.mli. - The examples teach one idea each. The numbered ones run on any machine.
- The hardware notes record what the GPUs do that their vendors' documentation does not say.
- The tests and the benches are for those who change rig.
Credits and licence #
rig learned to drive AMD and NVIDIA GPUs and Broadcom NICs from tinygrad's
runtimes, driver-less AMD path included. Its register, packet and interface
layouts are excerpts that generators copy from pinned commits of AMD's
amdgpu and amdkfd kernel drivers, ROCm's runtime and profilers, AMD's
PAL, NVIDIA's open kernel modules and open-gpu-doc, Linux's nouveau, mlx5
and bnxt_re headers, rdma-core, DPDK's bnxt driver, and LLVM. A
driver-less open boots the GPU with firmware from linux-firmware. rig
ships no firmware, and rig firmware fetches the images it pins by
digest.
rig is distributed under the ISC licence, as raven is. Each generated file
names its sources and carries their licences. Code derived from tinygrad
keeps its MIT licence in LICENSE-tinygrad, the
excerpts of LLVM keep theirs in LICENSE-llvm, and the
copy kernels in lib/amd/kernel and lib/nv/kernel keep theirs beside
them.