# gizhw:gpu

a GPU found at run time through its vendor's driver library, opened with gizlang:native (nothing is linked or required at build time). AMD GPUs run LLVM IR compiled in-process by ROCm's code object manager (comgr, no clang); NVIDIA GPUs run PTX the CUDA driver compiles; Apple GPUs run the MSL that the system compiler turns into a pipeline (gizhw:metal, unified memory: a copy to the device is a plain copy). A program uses one `Gpu` for device memory (`Buffer`, freed when its last copy is dropped), kernels compiled once per source and launches; gizml:tensor generates the kernels.

```gizlang
import * as Gpu from "gizhw:gpu";
```

## Types

### `Vendor`

```gizlang
type Vendor = { compiler: Option<Library>; metal: Option<Metal>; sources: Option<Sources>; }
```

What only one vendor has: HIP's comgr, which compiles; Metal's driver, whose calls are Objective-C messages.

- `compiler`: `_gizlang_sdk_core.Option<gizlang_native.Library>`
- `metal`: `_gizlang_sdk_core.Option<M13.Metal>`
- `sources`: `_gizlang_sdk_core.Option<M10.Sources>`

### `Sources`

```gizlang
type Sources = { sys: System; folder: string; }
```

GIZLANG_GPU_SOURCES: the folder each compiled kernel's source is written to (to read its PTX, SASS or IR).

- `sys`: `gizlang_system.System`
- `folder`: `string`

### `KernelTime`

```gizlang
type KernelTime = { key: string; entry: u64; blocks: i64; launches: i64; ns: i64; }
```

The time of one kernel's launches on `blocks` workgroups while profiling: `key` as compiled, `entry` its address.

- `key`: `string`
- `entry`: `u64`
- `blocks`: `i64`
- `launches`: `i64`
- `ns`: `i64`

### `Launching`

```gizlang
type Launching = { entry: u64; shared: i64; after: bool; }
```

A kernel's launch facts: `shared` bytes of dynamic workgroup memory, `after` its programmatic dependency (awaitGrid).

- `entry`: `u64`
- `shared`: `i64`
- `after`: `bool`

### `Gpu`

```gizlang
type Gpu
```

An opened GPU: `kind` is "hip" (AMD, ROCm), "cuda" (NVIDIA) or "metal" (Apple), `arch` its
instruction set ("gfx1151", "sm_120", "apple9"), the features its kernels are made for.

- `kind`: `string`
- `arch`: `string`
- `name`: `string`

### `Buffer`

```gizlang
type Buffer
```

`bytes` of device memory (`count` f32s), freed when the last copy is dropped.

- `count`: `i64`
- `bytes`: `i64`

### `Selection`

```gizlang
type Selection = | { kind: "auto"; } | { kind: "cpu"; } | { kind: "gpu"; } | { kind: "hip"; } | { kind: "cuda"; } | { kind: "metal"; }
```

Placement policy, distinct from whether a driver can be opened.

- `auto`
- `cpu`
- `gpu`
- `hip`
- `cuda`
- `metal`

### `GpuUsage`

```gizlang
type GpuUsage = { taken: i64; used: i64; peak: i64; }
```

Device bytes of a Gpu: `taken` from the driver now (in use or pooled), `used` by live buffers now, and `peak` the
most used since Gpu.resetPeak. A pooled block is not used: a peak of taken bytes counted the pool's idle blocks of
other sizes and grew with the free memory (flux3-klein refused every job, 10/21).

- `taken`: `i64`
- `used`: `i64`
- `peak`: `i64`

### `GpuError`

```gizlang
type GpuError = | { kind: "memory"; requested: i64; room: i64; } | { kind: "driver"; what: string; code: i64; }
```

Why a device call failed: the card has no room for `requested` bytes (`room` it has: its free bytes less the reserve),
or the driver gave the status `code` for the call `what`. A program that cannot go on without the card lets it fault
(R004 at the call); one that can, catches it: `try { ... } catch (e) { ... }`.

- `memory`
- `driver`

### `DeviceMemory`

```gizlang
type DeviceMemory = { free: i64; total: i64; }
```

Bytes of device memory: `free` for new buffers now (the pool of dropped buffers not counted), `total` of the GPU.

- `free`: `i64`
- `total`: `i64`

### `Pinned`

```gizlang
type Pinned
```

Page-locked host memory of `bytes`: the GPU copies it by DMA at the bus's rate (Gpu.copyAhead). Freed when the
last copy is dropped.

- `bytes`: `i64`

### `Recording`

```gizlang
type Recording
```

Kernels that Gpu.record launched once, launched again all together by Gpu.replay (CUDA and HIP graphs; Metal keeps
the launches and encodes them again).

Opaque: create and use it through this module's functions.

### `FileRead`

```gizlang
type FileRead = { buffer: Buffer; at: i64; offset: i64; size: i64; }
```

One read of Gpu.readFiles: `size` bytes of the file from byte `offset` into `buffer` from byte `at`.

- `buffer`: `M10.Buffer`
- `at`: `i64`
- `offset`: `i64`
- `size`: `i64`

## Functions

### `parseSelection`

```gizlang
function parseSelection(value: string): Selection throws string
```

The Selection a name gives: "auto" (or empty), "cpu", "gpu", "hip", "cuda" or "metal".

Effects: fail.

### `open`

```gizlang
function open(sys: System): Option<Gpu>
```

The GPU that `GIZLANG_DEVICE` selects (`auto` or unset: HIP, else CUDA, else Metal, else none; `cpu`: none). Tensors
go there with Tensor.on, and each operation runs where its operands are. An invalid value, or a required GPU
that does not open, faults rather than falling back to the CPU.

Effects: fail, read, write, io.

### `openApple`

```gizlang
function openApple(nat: Native, objc: Objc): Option<Gpu>
```

The Apple GPU that `objc` (gizhw:metal's Objc: the system's, or a test's) finds, opened as a Gpu.

Effects: fail, read, write, io.

### `loadTunings`

```gizlang
function loadTunings(sys: System, gpu: Gpu, folder: string): void
```

Takes this GPU model's tunings kept under `folder`, when there are any.

Effects: fail, read, write, io.

### `saveTunings`

```gizlang
function saveTunings(sys: System, gpu: Gpu, folder: string): bool throws string
```

Keeps what this GPU has measured under `folder` for the next process (loadTunings).

Effects: fail, read, write, console, io.

## Methods

### `Buffer.address`

```gizlang
function Buffer.address(self: Buffer): u64
```

A kernel's device address for launch arguments.

### `Buffer.view`

```gizlang
function Buffer.view(self: Buffer, at: i64, size: i64): Buffer
```

Bytes [at, at + size) of the buffer as a buffer of their own on the same memory (no copy); it keeps this one.

Effects: fail.

### `Selection.required`

```gizlang
function Selection.required(self: Selection): bool
```

Whether the selection fails without a GPU: `gpu`, `hip`, `cuda` and `metal` do; `auto` and `cpu` do not.

### `Gpu.others`

```gizlang
function Gpu.others(self: Gpu): Vec<Gpu>
```

The driver's other GPUs, in its order (CUDA: each device CUDA_VISIBLE_DEVICES shows but this one; none elsewhere
yet). Each opened is its own Gpu, current on this thread after it opens: Gpu.enter makes one current again. One of
this GPU's model shares its tunings (Gpu.tune).

Effects: fail, read, write, io.

### `Gpu.enter`

```gizlang
function Gpu.enter(self: Gpu): void
```

Makes this GPU the one the thread's calls go to (CUDA: its context current; nothing elsewhere, one GPU only).

Effects: fail, read, io.

### `GpuError.message`

```gizlang
function GpuError.message(self: GpuError): string
```

The error as one line for a person.

Effects: fail.

### `Gpu.room`

```gizlang
function Gpu.room(self: Gpu): i64 throws GpuError
```

The device bytes a new allocation may take now: the free bytes less the card's reserve (what a caller that sizes
its work by free memory must compare with: Gpu.memory's free includes the reserve).

Effects: fail, read, write, io.

### `Gpu.taken`

```gizlang
function Gpu.taken(self: Gpu): i64
```

The device bytes this Gpu holds from the driver now: its buffers and its pool of dropped ones.

Effects: fail, read.

### `Gpu.used`

```gizlang
function Gpu.used(self: Gpu): i64
```

The device bytes of this Gpu's live buffers now (not its pool).

Effects: fail, read.

### `Gpu.peak`

```gizlang
function Gpu.peak(self: Gpu): i64
```

The most device bytes of live buffers since the last Gpu.resetPeak: what a piece of work needs, measured.

Effects: fail, read.

### `Gpu.resetPeak`

```gizlang
function Gpu.resetPeak(self: Gpu): void
```

Starts measuring Gpu.peak from the bytes used now.

Effects: fail, read, write.

### `Gpu.fits`

```gizlang
function Gpu.fits(self: Gpu, size: i64): bool throws GpuError
```

Whether `size` more bytes fit on the card now: its free memory, with the pool of dropped buffers given back, less
the reserve kept for the driver and the card's other processes. Where they do not, the values no one reaches are
collected first (collected).

Effects: fail, read, write, io.

### `Gpu.buffer`

```gizlang
function Gpu.buffer(self: Gpu, count: i64): Buffer throws GpuError
```

`count` f32s of device memory, contents undefined.

Effects: fail, read, write, io.

### `Gpu.bytes`

```gizlang
function Gpu.bytes(self: Gpu, size0: i64): Buffer throws GpuError
```

`size` bytes of device memory, contents undefined (`count` is size / 4).

Effects: fail, read, write, io.

### `Gpu.trim`

```gizlang
function Gpu.trim(self: Gpu): void
```

Gives the blocks of dropped buffers back to the GPU (Gpu.memory then counts them free), after the work before.

Effects: fail, read, write, io.

### `Gpu.memory`

```gizlang
function Gpu.memory(self: Gpu): DeviceMemory throws GpuError
```

The free and total bytes of the GPU's memory now (DeviceMemory).

Effects: fail, read, write, io.

### `Gpu.upload`

```gizlang
function Gpu.upload(self: Gpu, data: Vec<f32>): Buffer throws GpuError
```

A device copy of `data`.

Effects: fail, read, write, io.

### `Gpu.download`

```gizlang
function Gpu.download(self: Gpu, buffer: Buffer, count: i64 = -1, at: i64 = 0): Vec<f32>
```

`count` f32s of the buffer from f32 `at` (to its end when -1), after every kernel launched before.

Effects: fail, read, write, io.

### `Pinned.address`

```gizlang
function Pinned.address(self: Pinned): u64
```

The device-visible host address of the memory.

### `Gpu.pinned`

```gizlang
function Gpu.pinned(self: Gpu, size: i64): Pinned
```

`size` bytes of page-locked host memory, contents undefined.

Effects: fail, read, io.

### `Gpu.readInto`

```gizlang
function Gpu.readInto(self: Gpu, buffer: Buffer, p: Pinned, at: i64): void
```

Copies `buffer` to `p` from byte `at`, after every kernel launched before.

Effects: fail, read, write, io.

### `Gpu.copyAhead`

```gizlang
function Gpu.copyAhead(self: Gpu, target: Buffer, p: Pinned, at: i64): void
```

Starts a copy of `target.bytes` bytes of `p` from byte `at` to `target` once the work launched before is done (so
the kernels before may still read `target`). The copy runs beside the kernels launched after it: the first that
reads `target` comes after Gpu.awaitCopies.

Effects: fail, read, write, io.

### `Gpu.awaitCopies`

```gizlang
function Gpu.awaitCopies(self: Gpu): void
```

The kernels launched after this run once every copy Gpu.copyAhead started before is done.

Effects: fail, read, io.

### `Gpu.now`

```gizlang
function Gpu.now(self: Gpu): i64
```

Nanoseconds of a monotonic clock, for timing work on the GPU.

Effects: fail, io.

### `Gpu.stream`

```gizlang
function Gpu.stream(self: Gpu): u64
```

The stream every kernel launches on (CUDA, HIP), for a vendor library whose work must take its place in order with
them; 0 on Metal.

### `Gpu.sync`

```gizlang
function Gpu.sync(self: Gpu): void throws GpuError
```

Waits for every launched kernel (a fault in one fails here).

Effects: fail, read, write, io.

### `Gpu.record`

```gizlang
function Gpu.record<F>(self: Gpu, work: F): Recording
```

Records the kernels that `work` launches, after the work before it is done. Each replay runs them again on the
same buffers: the buffers made in `work` stay the recording's until release, and the ones made before that it reads
must live as long. `work` must not wait for the GPU: no download, sync, profile, or tune that is not known yet
(run `work` once before). An upload in `work` is copied once, while recording: its values are the recording's.

### `Gpu.replay`

```gizlang
function Gpu.replay(self: Gpu, r: Recording): void
```

Runs the recorded kernels again, after the work before (returns at once, as a launch does).

Effects: fail, read, write, io.

### `Gpu.release`

```gizlang
function Gpu.release(self: Gpu, r: Recording): void
```

Frees a recording after the work before. Its buffers that were dropped go back to the pool, and the others do
when they are dropped.

Effects: fail, read, write, io.

### `Gpu.write`

```gizlang
function Gpu.write(self: Gpu, buffer: Buffer, data: Vec<f32>, at: i64 = 0): void
```

Copies `data` into the f32s of `buffer` from f32 `at`, after the work before.

Effects: fail, read, write, io.

### `Gpu.kernel`

```gizlang
function Gpu.kernel<F>(self: Gpu, key: string, name: string, source: F): u64
```

The kernel `name` of the program `source()` makes (LLVM IR for "hip", PTX for
"cuda"), made and compiled the first time this Gpu sees `key`: a key names one
program, and costs less to compare than the program to make.

### `Gpu.launch`

```gizlang
function Gpu.launch(self: Gpu, kernel: u64, blocks: i64, threads: i64, arguments: Vec<u64>): void throws GpuError
```

Launches KERNEL on BLOCKS blocks of THREADS threads with ARGUMENTS (the addresses of the argument values).

Effects: fail, read, write, io.

### `Gpu.tensorMap`

```gizlang
function Gpu.tensorMap(self: Gpu, b: Buffer, rowBytes: i64, rows: i64, pitch: i64, boxBytes: i64, boxRows: i64, swizzle: i64): Buffer throws GpuError
```

The copy engine's map (NVIDIA, Kernel.hasTma) of `rows` rows of `rowBytes` bytes, `pitch` bytes apart, in `b`:
Kernel.tensorLoad copies boxes of `boxRows` rows of `boxBytes` bytes (a multiple of 16, at most 256) from it, with
zeros past its edges. `swizzle` 0, 32, 64 or 128 permutes the 16-byte chunks of each box row in that span (boxBytes
at most `swizzle`), as the chunk XOR of ldmatrix reads wants. The map is in a buffer of its own; its address is the
kernel's argument.

Effects: fail, read, write, io.

### `Gpu.startProfile`

```gizlang
function Gpu.startProfile(self: Gpu): void
```

Starts profiling (and clears the times): each launch from now on waits for the work before it and for itself, so
the times are the kernels' own, and the program runs slower.

Effects: fail, write.

### `Gpu.profiling`

```gizlang
function Gpu.profiling(self: Gpu): bool
```

Whether startProfile is on (a recording cannot be made then).

Effects: fail, read.

### `Gpu.profile`

```gizlang
function Gpu.profile(self: Gpu): Vec<KernelTime>
```

The time of each kernel launched since startProfile, the slowest first; profiling stops.

Effects: fail, read, write.

### `Gpu.slab`

```gizlang
function Gpu.slab(self: Gpu, sizes: Vec<i64>): Vec<Buffer>
```

Buffers of `sizes` bytes in one block of device memory, each from a multiple of 256 bytes. The driver gives each block
in pages of 2 MiB: many large buffers of their own lose up to a page each (the 1146 weights of q27-4 lost 475 MiB).

Effects: fail, read, write, io.

### `Gpu.uploadBytes`

```gizlang
function Gpu.uploadBytes(self: Gpu, data: bytes): Buffer
```

A device copy of `data`'s bytes.

Effects: fail, read, write, io.

### `Gpu.uploadFile`

```gizlang
function Gpu.uploadFile(self: Gpu, fd: i64, offset: i64, size: i64): Result<Buffer, string>
```

A device buffer of `size` bytes of the open file `fd` from byte `offset`: pread straight into page-locked memory a
piece at a time, each piece copied by DMA while the next one reads (no other copy on the host).

Effects: fail, read, write, io.

### `Gpu.readFiles`

```gizlang
function Gpu.readFiles(self: Gpu, fd: i64, reads: Vec<FileRead>): Result<bool, string>
```

Reads each of `reads` from the open file `fd`, after every kernel launched before. On unified memory (Metal) the
reads go straight into the buffers, else through the staging memory.

Effects: fail, read, write, io.

### `Gpu.downloadBytes`

```gizlang
function Gpu.downloadBytes(self: Gpu, buffer: Buffer, size: i64 = -1, at: i64 = 0): bytes
```

`size` bytes of the buffer from byte `at` (to its end when -1), after every kernel launched before.

Effects: fail, read, write, io.

### `Gpu.writeBytes`

```gizlang
function Gpu.writeBytes(self: Gpu, buffer: Buffer, data: bytes, at: i64 = 0): void
```

Copies `data` into the buffer from byte `at`, after the work before.

Effects: fail, read, write, io.

### `Gpu.compile`

```gizlang
function Gpu.compile<F>(self: Gpu, key: string, make: F): u64
```

The Kernel `make()` builds, compiled for this GPU the first time it sees `key`
(a key names one kernel, and costs less to compare than the kernel to build).

### `Gpu.slots`

```gizlang
function Gpu.slots(self: Gpu, kernel: u64, threads: i64): i64
```

The workgroups of `threads` threads of `kernel` that run at once on the whole GPU (one wave), or 0 where the
driver does not say (AMD).

Effects: fail, read, io.

### `Gpu.run`

```gizlang
function Gpu.run(self: Gpu, kernel: u64, blocks: i64, threads: i64, buffers: Vec<Buffer>, scalars: Vec<u64>): void
```

Runs `kernel` on `blocks` workgroups of `threads` threads: its buffer
parameters are `buffers` in order, then its scalars.

Effects: fail, read, write, io.

### `Gpu.copyRows`

```gizlang
function Gpu.copyRows(self: Gpu, dtype: i64, dst: Buffer, src: Buffer, outer: i64, len: i64, srcRow: i64, srcOff: i64, dstRow: i64, dstOff: i64): void
```

dst[o * dstRow + dstOff + j] = src[o * srcRow + srcOff + j] for o < outer, j < len (`dtype` elements).

Effects: fail, read, write, console, io.

### `Gpu.zero`

```gizlang
function Gpu.zero(self: Gpu, b: Buffer, first: i64, count: i64): void
```

Zeroes `count` words of 4 bytes of b from word `first`.

Effects: fail, read, write, console, io.

### `Gpu.tune`

```gizlang
function Gpu.tune<F>(self: Gpu, key: string, choices: Vec<i64>, work: F): i64
```

The fastest candidate, measured after compilation and cached on this opened GPU.
`key` names one operation, shape, layout and dtype; candidates must give the same result.
`work` may overwrite its output and runs once per choice to warm up, then three timed times per choice, the
choices in turn: work of another process on the GPU then slows each choice alike.

### `Gpu.tunings`

```gizlang
function Gpu.tunings(self: Gpu): string
```

The choices tune measured on this GPU, one "CHOICE KEY" line each: a later process takes them (Gpu.learn) instead
of measuring again.

Effects: fail, read.

### `Gpu.learn`

```gizlang
function Gpu.learn(self: Gpu, text: string): void
```

Takes choices of Gpu.tunings' text (lines it cannot read are skipped): tune measures only keys it has not seen. A
key names an operation and its shape, not the GPU, so keep a text for each GPU model (tuningsFile).

Effects: fail, read, write.

### `Gpu.tuningsFile`

```gizlang
function Gpu.tuningsFile(self: Gpu, folder: string): string
```

The file under `folder` that keeps this GPU model's tunings.

Effects: fail.

### `Gpu.force`

```gizlang
function Gpu.force(self: Gpu, position: i64): void
```

From now on each tune picks its candidate at `position` (the last where it has fewer), measures nothing and keeps
nothing; -1 tunes again. A test runs each candidate with it.

Effects: fail, write.

### `Gpu.useVendorBlas`

```gizlang
function Gpu.useVendorBlas(self: Gpu, on: bool): void
```

On: from now on a product without an epilogue may also run as cuBLAS's (cublasGemmEx, as PyTorch's matmul), where
the dynamic loader finds libcublas.so.12 and the tuner measures it faster. Its order of additions is NVIDIA's, so
its bits can differ from the CPU's. Off (the default): every product has the CPU's bits.

Effects: fail, read, write, io.

### `Gpu.vendorBlas`

```gizlang
function Gpu.vendorBlas(self: Gpu): bool
```

Whether products may run as cuBLAS's: useVendorBlas(true) was called and the library loaded.

Effects: fail, read.

### `Gpu.same`

```gizlang
function Gpu.same(self: Gpu, other: Gpu): bool
```

Whether two Gpus are one opened GPU (copies of it).

### `Gpu.vendorMatmul`

```gizlang
function Gpu.vendorMatmul(self: Gpu, c: Buffer, a: Buffer, b: Buffer, input: string, output: string, m: i64, k: i64, n: i64, transposeA: bool, transposeB: bool): void
```

C = op(A) op(B) by cuBLAS (Gpu.vendorBlas must be true): row-major m x n C of `output`, A m x k (stored k x m if
transposeA) and B k x n (stored n x k if transposeB) of `input` ("f32", "f16" or "bf16"), added in f32.

Effects: fail, read, io.

## Constants

### `DEVICE_ENV`

```gizlang
const DEVICE_ENV = "GIZLANG_DEVICE"
```

The name of the environment variable that chooses the device.

