# gizml:netops

the operations of networks on gizml:tensor Tensors. Each one runs where its operand is, on the CPU or a GPU, and adds in one order on both: row terms and norms, losses, argmax, attention (plain, packed rows, 8-bit matrix cores), dropout, convolution and pooling, the modulated norm with INT8 rows, and the rows of a language model's step (argmax, sampling candidates, split sums, rotated norm and SwiGLU).

```gizlang
import * as Netops from "gizml:netops";
```

## Types

### `HeadPrep`

```gizlang
type HeadPrep = { qw: Tensor; kw: Tensor; cos: Tensor; sin: Tensor; eps: f64; }
```

Each head of q and k over its RMS (eps) times its norm weight (qw, kw: `[head]` F32), then its values e < 2 half
turned with e ± half by its row's cos and sin ([rows, half] F32, half = 0: none): a transformer's QK norm and rotary
(H3's split halves; FLUX.3's pairs, loaded even values first). The quantized path applies it as its passes read q
and k; the BF16 path first in place (prepKernel), so qkv's q and k may change.

- `qw`: `M32.Tensor`
- `kw`: `M32.Tensor`
- `cos`: `M32.Tensor`
- `sin`: `M32.Tensor`
- `eps`: `f64`

## Functions

### `affine`

```gizlang
function affine(x: Tensor, w: Tensor, b: Tensor, compute: i64): Tensor
```

x Wᵀ + b ([rows, out] f32), the products in `compute` on a GPU (as lazyMatmulIn).

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

### `layerNorm`

```gizlang
function layerNorm(x: Tensor, weight: Tensor, bias: Tensor, eps: f64): Vec<Tensor>
```

Layer normalization over the last axis: (x - mean) / sqrt(var + eps) * weight
+ bias, with mean and var over the row (1/n, as torch). Returns the output and
each row's mean and 1/sqrt(var + eps), which layerNormGrad takes.

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

### `layerNormGrad`

```gizlang
function layerNormGrad(dy: Tensor, x: Tensor, weight: Tensor, mean: Tensor, rstd: Tensor): Vec<Tensor>
```

The gradients of layerNorm against x, weight and bias from the output's
gradient dy and layerNorm's row means and rstds: dx = rstd (dy w - mean(dy w)
- xhat mean(dy w xhat)); dweight and dbias add over rows in order.

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

### `crossEntropyLoss`

```gizlang
function crossEntropyLoss(logits: Tensor, labels: Vec<i64>): f64
```

Softmax cross-entropy of logits (rows, classes) against integer labels: each
row's loss m + log(sum exp(x - m)) - x[label] (the sum in 16 lanes), and their
mean in f64 in row order.

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

### `crossEntropyRows`

```gizlang
function crossEntropyRows(logits: Tensor, labels: Vec<i64>): Tensor
```

Each row's loss of crossEntropyLoss, as a tensor of shape `[rows]` where the logits are (on a GPU: no wait).

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

### `crossEntropyGrad`

```gizlang
function crossEntropyGrad(logits: Tensor, labels: Vec<i64>, scale: f64): Tensor
```

Its gradient times `scale` (the loss's gradient over rows): (softmax - onehot) scale,
the softmax exp(x - m) times the f32 of 1/sum.

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

### `argmaxInto`

```gizlang
function argmaxInto(g: Gpu, rows: i64, vocab: i64, stride: i64, indirect: bool, top: u64, logits: u64, blocks: u64): void
```

argmaxKernel over `rows` rows of the logits at `logits` (rows of `stride`, of the head blocks in `blocks` when
`indirect`), each row's token as f32 into `top`.

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

### `candidatesInto`

```gizlang
function candidatesInto(g: Gpu, rows: i64, vocab: i64, stride: i64, indirect: bool, cand: u64, logits: u64, blocks: u64): void
```

candidatesKernel over `rows` rows of the logits at `logits` (as argmaxInto reads them) into `cand`.

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

### `splitSumInto`

```gizlang
function splitSumInto(g: Gpu, y: u64, p: u64, total: i64, splits: i64, add: bool): void
```

splitSumKernel over the `total` f32 values at y and the parts at p.

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

### `normRotateInto`

```gizlang
function normRotateInto(g: Gpu, rows: i64, width: i64, eps: f64, rotate: bool, stride: i64, offset: i64, y: u64, x: u64, w: u64, d: u64, map: u64): void
```

normRotateKernel over `rows` rows of `width` (the row map at `map`, as f32 indexes).

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

### `siluMulRotatedInto`

```gizlang
function siluMulRotatedInto(g: Gpu, rows: i64, ffn: i64, nUp: i64, y: u64, p: u64, d: u64): void
```

siluMulKernel over `rows` rows.

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

### `attention`

```gizlang
function attention(q: Tensor, k: Tensor, v: Tensor, scale: f64, causal: bool, rate: f64, key: Key): Tensor
```

Scaled dot-product attention, softmax(q kᵀ scale) v, without the (tq, tk) matrix of scores: q (b, tq, d), k
(b, tk, d) and v (b, tk, e) give (b, tq, e). With `causal`, query i sees the keys up to i + tk - tq (a decoder's
mask; with a KV cache tq < tk). With `rate` > 0 each weight is dropped at that rate (the mask of `key`'s word
(b tq + i) tk + j, as Tensor.dropout draws it for the weights) and the rest scaled by 1 / (1 - rate).

The CPU and a GPU take the same steps in f32, so they give the same bits: the keys go in blocks of 16; each score
adds q[p] k[j][p] by fma for p from 0, then is scaled; the block's maximum m' gives c = e^(m - m') and the weights
e^(s - m'); the sum l becomes l c plus the weights in order, and each output element a becomes a c plus the
weights times the values by fma in order; the result is a / l. attentionRows is the fast path on matrix cores for
a transformer's packed BF16 rows.

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

### `causalMask`

```gizlang
function causalMask(tq: i64, tk: i64): Tensor
```

The additive mask of causal attention, (tq, tk): 0 where query i sees key j (j <= i + tk - tq), else -inf.

Effects: fail.

### `im2col`

```gizlang
function im2col(x: Tensor, kh: i64, kw: i64, stride: i64, pad: i64): Tensor
```

The patches of x (n, c, h, w) under a kh x kw window at `stride` with `pad`
zeros around: (n * oh * ow, c * kh * kw), so a convolution is one matmul.

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

### `patchRows`

```gizlang
function patchRows(x: Tensor, kh: i64, kw: i64, pad: i64, r0: i64, r1: i64, dtype: i64): Tensor
```

The patches of x ([c, h, w]) under a kh x kw window (stride 1, `pad` zeros around) for output rows r0 to r1, laid
by patch entry: [c kh kw, (r1 - r0) ow] of `dtype` (ow = w + 2 pad - kw + 1), entry (ch kh kw + i kw + j,
(oy - r0) ow + ox) = x[ch, oy + i - pad, ox + j - pad]. W [o, c kh kw] times it is the convolution's rows r0 to r1,
channel first, with no padded copy of x; on a GPU its reads and its writes both run along pixels.

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

### `lazyConv`

```gizlang
function lazyConv(w: Tensor, x: Tensor, kh: i64, kw: i64, pad: i64, compute: i64): Lazy
```

w ([o, c kh kw]) times the patches of x ([c, h, w]) under a kh x kw window at stride 1 with `pad` zeros around
(patchRows): the convolution, [o, oh ow] channel first, not yet computed, so its bias and what follows run in the
product's store. On NVIDIA's matrix cores (BF16, 3 x 3, pad 1, w a multiple of 8) the product reads the patches
from x as it copies them (Product.patch, patchFetch), so they are never stored; elsewhere they are built first.

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

### `lazyConv3`

```gizlang
function lazyConv3(w: Tensor, x: Tensor): Lazy
```

w ([o, 27 c], its inputs in (time, channel, height, width) order) over frames 1 .. n of x ([n + 2, c, h, w] BF16,
the edge frames 0 and n + 1 given): the 3 x 3 x 3 convolution with zeros around in space, [n, o, h w], not yet
computed. On NVIDIA's matrix cores (w a multiple of 8) it is one product of n batches whose B is the patches of
frames t .. t + 2 as an image of 3 c channels (Product.patch); elsewhere a lazyConv a frame, each computed in F32.

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

### `pixelNormInto`

```gizlang
function pixelNormInto(x: Tensor, eps: f64, act: bool, out: Tensor): void
```

PixelNorm of x ([frames, c, pixels] BF16 or F32 on a GPU, channel first in each frame): each pixel's c values over
their RMS (eps), then SiLU when `act`, into out (x's shape and dtype; x itself is allowed): StyleGAN's and LTX's VAE
norm.

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

### `groupNorm`

```gizlang
function groupNorm(x: Tensor, groups: i64, w: Tensor, b: Tensor, eps: f64, act: bool, dtype: i64): Tensor
```

GroupNorm of x ([c, ...] f32, channel first): each of `groups` groups of c / groups channels less its mean, over
the square root of its variance plus eps, times w[ch] plus b[ch], then SiLU when `act`: `dtype` elements of x's
shape. On a GPU a group's sums run on GROUP_SLICES workgroups (f32 within one, f64 across), so a few long groups
fill the GPU; the host adds in f64.

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

### `col2im`

```gizlang
function col2im(cols: Tensor, n: i64, c: i64, h: i64, w: i64, kh: i64, kw: i64, stride: i64, pad: i64): Tensor
```

The adjoint of im2col: element (b, ch, y, x) adds every patch entry that
copied it, window offsets (i, j) in increasing order.

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

### `im2col1d`

```gizlang
function im2col1d(x: Tensor, k: i64, stride: i64, pad: i64, dilation: i64, first: i64 = 0, count: i64 = -1): Tensor
```

The patches of x (t, c), time first, under a window of k taps `dilation` apart at `stride`, with `pad` zeros
before and after: (ot, c * k), entry (o, ch * k + j) = x[o * stride + j * dilation - pad, ch], so a 1-D
convolution is one matmul (conv1d). With `count` >= 0, only the `count` rows from row `first`.

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

### `col2im1d`

```gizlang
function col2im1d(cols: Tensor, c: i64, length: i64, k: i64, stride: i64, pad: i64): Tensor
```

The adjoint of im2col1d at dilation 1 on cols (n, c * k): the (length, c) whose entry (o, ch) adds every
cols[i, ch * k + j] with i * stride + j - pad = o, j in increasing order. A transposed convolution is the matmul
x W then this (convTranspose1d).

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

### `conv1d`

```gizlang
function conv1d(x: Tensor, w: Tensor, bias: Vec<Tensor>, stride: i64, pad: i64, dilation: i64): Tensor
```

The 1-D convolution of x (t, c), time first, by w (o, c, k) with `bias` (o) unless empty: (ot, o), as PyTorch's
conv1d of xᵀ transposed.

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

### `convTranspose1d`

```gizlang
function convTranspose1d(x: Tensor, w: Tensor, bias: Vec<Tensor>, stride: i64, pad: i64): Tensor
```

The 1-D transposed convolution of x (n, c), time first, by w (c, o, k) at `stride` with `pad` with `bias` (o)
unless empty: ((n - 1) stride - 2 pad + k, o), as PyTorch's conv_transpose1d of xᵀ transposed.

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

### `pool2d`

```gizlang
function pool2d(x: Tensor, size: i64, stride: i64, average: bool): Tensor
```

Max (or, `average`, mean) pooling of x (n, c, h, w) over k x k windows at
`stride`: the max is the first largest in window order; the mean adds the
window in order and multiplies by the f32 of 1/(k k).

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

### `pool2dGrad`

```gizlang
function pool2dGrad(dy: Tensor, x: Tensor, size: i64, stride: i64, average: bool): Tensor
```

The gradient of pool2d against x from dy: each element adds, over the windows
holding it in order, dy/(k k) (average) or dy where it is the window's max.

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

### `attentionRows`

```gizlang
function attentionRows(qkv: Tensor, heads: i64, head: i64, quantized: bool, group: i64, out: Tensor, prep: Vec<HeadPrep> = [], queries: i64 = 0): void
```

Full attention of every head of qkv ([n, 3 heads head] BF16 on a GPU: each row's q, k and v of `heads` heads of
`head`, rotated as the model needs) into out ([n, heads head] BF16): softmax(q kᵀ / sqrt(head)) v a head, the
keys of each group of `group` rows alone (0: all rows). With `quantized`, where the GPU has 8-bit matrix cores
(K.hasFp8Mma) and heads are 64 or 128, Q Kᵀ runs on INT8 and P V on E4M3 (attention8, SageAttention's split);
else on BF16. `prep` (a HeadPrep or none) normalizes and turns each head of q and k as attention reads them: the
8-bit path in its quantizing pass, the BF16 path in place in qkv first.

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

### `attendedRows`

```gizlang
function attendedRows(qkv: Tensor, heads: i64, head: i64, quantized: bool, group: i64, prep: Vec<HeadPrep> = [], queries: i64 = 0): Tensor
```

attentionRows' output as a new tensor where qkv is: [rows (queries when given), heads × head], BF16 on a GPU. On
the host in F32 and exact (`quantized` has no effect): prep, then attention among the rows of each group.

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

### `headPrepInPlace`

```gizlang
function headPrepInPlace(qkv: Tensor, n: i64, heads: i64, head: i64, p: HeadPrep, row0: i64): void
```

p of q and k of the first n rows of qkv ([rows, 3 heads head] BF16 on a GPU, each row its q, k and v) in place
(prepKernel), `row0` the sequence row of the first.

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

### `crossAttentionRows`

```gizlang
function crossAttentionRows(q: Tensor, kv: Tensor, heads: i64, head: i64, out: Tensor): void
```

Attention of the rows of q ([nq, heads head] BF16 on a GPU) over the keys and values of kv ([nk, 2 heads head]:
each row's k, then its v), as attentionRows: a cross attention (a text's, another modality's). Into out ([nq, heads
head] BF16), on the BF16 matrix cores.

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

### `neighborhood3d`

```gizlang
function neighborhood3d(qkv: Tensor, grid: Vec<i64>, kernel: Vec<i64>, heads: i64, scale: f64): Tensor
```

3D neighborhood attention (NATTEN's na3d: dilation 1, not causal) of qkv ([T H W, 3 heads head] BF16 on a GPU, F32
on the host; rows in (t, h, w) order, each row its q, k and v, each its heads, as a fused QKV linear gives them; as
`attention` reads it): each query attends
the keys of a window of `kernel` [kt, kh, kw] positions (each odd) around it, shifted inward at the grid's edges and
as long as the axis at most, with scores q · k times `scale`. The output has q's layout, BF16 on a GPU (flash attention
over the box of keys each tile of 64 queries can see), F32 on the host (exact).

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

### `modulatedNorm`

```gizlang
function modulatedNorm(x: Tensor, w: Tensor, shift: Tensor, scale: Tensor, map: Tensor, eps: f64, centered: bool = false): Tensor
```

Each row of x ([rows, width] F32) over its RMS (with `centered`, LayerNorm's: less its mean, over its standard
deviation), times w, times 1 + scale and plus shift of its modulation row (map[r], an F32 row index into shift and
scale): a diffusion transformer's adaptive norm (AdaLN). F32 on the host, BF16 on a GPU.

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

### `modulatedNormInto`

```gizlang
function modulatedNormInto(x: Tensor, w: Tensor, shift: Tensor, scale: Tensor, map: Tensor, eps: f64, y: Tensor, centered: bool = false): void
```

modulatedNorm on a GPU into y (BF16).

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

### `gatedNormAddInto`

```gizlang
function gatedNormAddInto(x: Tensor, y: Tensor, w: Tensor, gate: Tensor, map: Tensor, eps: f64): void
```

x ([rows, width] F32, on a GPU) plus, each row, y's row (BF16) over its RMS (eps) times w, times the row map[r] of
gate ([modulation rows, width] F32): a block's output norm under its gate (Z-Image's sandwich norm). In place.

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

### `rowNormRopeInto`

```gizlang
function rowNormRopeInto(gpu: Gpu, at: u64, stride: i64, rows: i64, width: i64, head: i64, w: Tensor, eps: f64, cos: Vec<Tensor> = [], sin: Vec<Tensor> = []): void
```

Each of `rows` rows of `width` BF16 values (from address `at`, rows `stride` values apart, on `gpu`) over its RMS
across the whole row (eps), times w (`width` F32 values), then, with cos and sin ([rows, width / 2] F32), each head of
`head` values turned by split halves: value d < head / 2 with value d + head / 2 by its pair's angle (pair h head / 2
+ d of the row): LTX's QK norm, which spans every head, and its rotary. In place.

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

### `int8Rows`

```gizlang
function int8Rows(x: Tensor, bits: i64 = 8): Vec<Tensor>
```

Rows of x (BF16 on a GPU, a multiple of 256 wide) as INT8 over a scale a row, as ConvRot quantizes activations
(WanGP's asym_w4a8_int8, comfy-kitchen's int8_linear): each 256 values times the regular Hadamard matrix over 16
(Kernel.hadamard regular: its outputs in that order, so the weights take K.regularSource and K.regularSign), in F32
(y); s = max |y| / 127 a row ([rows, 1] F32, at least 1e-30); q = y / s to nearest even within [-128, 127]. Those
references round y and y / s to BF16 first, which moves a code by one where the BF16 step passes a half: LTX-2.5's
video velocity was 26.7% off its BF16-activation result with that rounding and 22.6% without it (ComfyUI's 20.8%).
[q, s]. With 4 `bits`, q is I4: s = max |y| / 7, q within [-8, 7]. On the host x holds BF16 values (as F32) and q
the integers (as F32): the same steps, the transform gizlang:linalg's.

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

### `int8RowsInto`

```gizlang
function int8RowsInto(x: Tensor, q: Tensor, s: Tensor): void
```

int8Rows into q ([rows, width] I8, or I4 for 4 bits) and s ([rows, 1] F32).

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

### `modulatedInt8RowsInto`

```gizlang
function modulatedInt8RowsInto(x: Tensor, w: Tensor, shift: Tensor, scale: Tensor, map: Tensor, eps: f64, y: Option<Tensor>, q: Tensor, s: Tensor, centered: bool = false): void
```

int8Rows of modulatedNorm's rows of x (F32) in one pass, into q and s; with `y` modulatedNorm's values there too.

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

## Methods

### `Tensor.argmax`

```gizlang
function Tensor.argmax(self: Tensor): Vec<i64>
```

The index of each row's largest element (the first of equals; NaN never wins).

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

### `Tensor.dropout`

```gizlang
function Tensor.dropout(self: Tensor, k: Key, p: f64): Tensor
```

x where word i of `key`'s draw (as `uniform` makes it) is at least p, scaled
by 1 / (1 - p), else 0: the same mask on the CPU and a GPU, so the backward
pass applies it again from the key.

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

## Constants

### `TOP_EACH`

```gizlang
const TOP_EACH = 4
```

The largest logits each of a candidates workgroup's 256 threads keeps of the values it reads, and the floats of a
row's candidates (a value and a token each: 256 TOP_EACH 2).

### `CANDIDATE_ROW`

```gizlang
const CANDIDATE_ROW = 2048
```

The floats of a row's candidates in a candidates workgroup.

