# GPU reference card

_This page covers the KDB-X GPU module APIs, including inputs, outputs, and examples for each function._

## GPU objects

Results that live on the GPU are returned as KDB-X virtual objects (type `112h`): GPU tables, keyed tables, vectors and dictionaries are _virtual tables_, and tensors are _virtual tensors_. Results on the CPU (for example `.gpu.from`, or `.gpu.lj` of two CPU tables) are plain q data. Every `.gpu` function accepts both virtual objects and plain q data.

Plain q works on a virtual table: `select`, `exec` (without `by`) and functional `?[T;c;b;a]` (including `select[n]` and `select[n;<c]`), `lj`, `ij`, `aj`, `aj0`, `ajf`, `ajf0`, `wj`, `wj1`, `sublist`, `` `c xasc T ``, `meta T`, `cols T`, `` `k xkey T `` and console display, which fetches only the rows shown. On a virtual tensor, `sublist` slices the leading axis as q's `sublist` slices a matrix, and console display fetches only the rows shown, inside a list or dictionary too. These keywords keep their native behavior for CPU arguments.

q primitives do not see inside a virtual object:

> **Warning — Primitives return wrong data, not an error**
>
> `#`, `_` and `@` cannot be intercepted, so q applies them to the virtual wrapper itself. `3#v` and `3#T` return copies of the whole object in a general list, and `1 _ v` and `v@1 3` signal `type` or `rank`. Never use them on a virtual object; use the `.gpu` functions below, or `sublist`, which does work.
>
> On a virtual tensor `X`, most q primitives signal `type`, `rank` or `length`, but these return a value that can pass
> for an answer:
>
> - The reductions and scans `sum`, `prd`, `min`, `max`, `sums`, `mins`, `maxs`, `deltas`, `ratios`, `fills`,
>   `prev`, `+/`, `*/`, `|/` and `+\`, and `first`, `last`, `reverse`, `{x} each X` and `2 mmax X` return `X`
>   itself, unchanged.
> - `inv X` returns `` `T ``, not a matrix inverse (`inv` is `!:`, so it is `key X`).
> - `X=Y`, `X in Y` and `X~Y` compare the wrappers, not the elements, and give one boolean: `1b` for the same object
>   (`X=X`), and `0b` for two tensors whose elements are equal.
> - `count X` and `count each X` are `1`, `2 mcount X` is `,1i`, `null X` is `0b`, `type X` is `112h`, `attr X` is
>   `` ` ``, `value X` is the wrapper dictionary, and `2#X`, `X,X` and `enlist X` are lists holding the wrapper.
>
> `raze X`, `distinct X` and `group X` work on a host copy and give q's answer.
>
| Instead of | Use |
|---|---|
| ``T`col`` or ``T[`col]`` | ``exec col from T`` |
| `count T` (returns `1`) | [`.gpu.count`](#count) `T` |
| `update`/`delete ... from T` | [`.gpu.update`](#update) |
| `n!T` | `` `k xkey T `` (and `() xkey K` to unkey) |
| `flip T`, `key T`, `value T`, `x!y` | [`.gpu.from`](#from) first |
| `n#v`, `n#T`, `` `c#T ``, `n _ v`, `v@i` | [`.gpu.take`](#take), [`.gpu.drop`](#drop), [`.gpu.gather`](#gather) |
| `type T`, `attr T` | [`.gpu.type`](#type), [`.gpu.attr`](#attr) |
| `sum X`, `max X`, `first X` ... on a tensor (return `X`) | `.gpu.tensor.sum[axes;keepdims;X]`, `.max`, `.first` ... ([reductions](#axis-aware-tensor-reductions)) |
| `sums X`, `maxs X` ... on a tensor (return `X`) | `.gpu.tensor.sums[axis;X]`, `.maxs` ... ([scans](#axis-aware-tensor-cumulative-scans)) |
| `inv X` (returns `` `T ``) | `.gpu.tensor.inv X` |
| `count X` (returns `1`) | `first .gpu.tensor.shape X` |
| `X=Y`, `X~Y` (compare the wrappers) | `.gpu.tensor.from` both first |
| `deltas`, `ratios`, `fills`, `prev`, `reverse`, `mmax`, `mcount` on a tensor | `.gpu.tensor.from` first, then the q primitive |

> **Note — Nested lists**
>
> A nested (list of lists) column can be transferred with [`.gpu.to`](#to), read back with [`.gpu.from`](#from), counted with [`.gpu.count`](#count), sliced by rows with [`.gpu.take`](#take) and [`.gpu.sublist`](#sublist), displayed at the console, and carried through [`.gpu.lj`](#lj) and the [`.gpu.aj`](#aj) family as a column of the left table.
> `.gpu.iasc`, `.gpu.idesc`, `.gpu.asc`, `.gpu.xasc`, `.gpu.xdesc`, `.gpu.gather`, `.gpu.append` and `.gpu.bin` reject it with `GPU_NOT_IMPLEMENTED`, and leave the object unchanged. So do `.gpu.aj`, `.gpu.aj0`, `.gpu.ajf` and `.gpu.ajf0` when a column of `t2` that they would return is nested, and `.gpu.ajf` and `.gpu.ajf0` when they would fill from a nested column of `t1` (`.gpu.lj` and `.gpu.ij` already reject a nested right-hand column).
> The console shows the first rows of a GPU object by slicing it on the GPU first; a nested object cannot be sliced
> there, so it is read back whole and sliced on the host, which copies the whole object.
>
Numeric keywords such as `sum`, `abs` and `mmu` are not overloaded; use [`.gpu.tensor`](#tensor) for tensors. A `select` on a tensor signals `type`.

## Keywords


<table class="kx-tight" markdown><tbody markdown>
--8<-- "modules/gpu/kwds.txt"
</tbody></table>

### By category

<table class="kx-tight" markdown><tbody markdown>
--8<-- "modules/gpu/kwdcat.txt"
</tbody></table>

---

> **Note**
>
> Examples use the following naming convention:
>
> - Lowercase names indicate CPU-resident objects (for example, `t`, `a`).
> - Uppercase names indicate GPU or mixed-residency objects (for example, `T`, `A`).
>
> ```q
> q).gpu:use`kx.gpu
> q)\S 42
> ```
>
## aj

_As-of join_
```syntax
.gpu.aj  [c; t1; t2]
.gpu.aj0 [c; t1; t2]
.gpu.ajf [c; t1; t2]
.gpu.ajf0[c; t1; t2]
```

Where:

- `t1` is a CPU, GPU or mixed-residency table
- `t2` is a CPU, GPU or mixed-residency table
- `c` is a symbol vector of `n` column names, common to `t1` and `t2`, and of matching type
- column `c`<sub>`n`</sub> is of a sortable type (typically time)

returns a table with records from the left-join of `t1` and `t2`.
In the join, columns `c`<sub>`0`</sub>`...c`<sub>`n-1`</sub> are matched for equality, and the last value of `c`<sub>`n`</sub> (most recent time) is taken.
For each record in `t1`, the result has one record with the items in `t1`, and

- if there are matching records in `t2`, the items of the last (in row order) matching record are appended to those of `t1`;
- otherwise the remaining columns are null.

> **Warning — Current limitations**
>
>
> - `c` is limited to one or two column names. The same limit applies to [`.gpu.bin`](#bin).
> - The last (ordered) key column must be long, timestamp, timespan, int, month, date, minute, second, or time, and must have the same type in `t1` and `t2`. The same limit applies to [`.gpu.bin`](#bin).
> - Other key columns must have the same type in `t1` and `t2`, and must be of a type supported by [`.gpu.group`](#group).
> - Enum key columns are matched by symbol, so their domains may differ between `t1` and `t2`, as in q. A symbol that `t2`'s domain does not contain finds no match, and its row gets nulls. The result keeps the key column of `t1` (so its domain), except that `aj0` and `ajf0` take every key column from `t2` in q but only the time column on the GPU: with different domains the enum key column of their result has the symbols of q's result but the domain of `t1`. A plain symbol column cannot be matched with an enum column (`GPU_NOT_IMPLEMENTED`).
>
>
```q
q)t:([]time:2026.01.01D10:01:01 2026.01.01D10:01:03 2026.01.01D10:01:04;sym:`msft`ibm`ge;qty:100 200 150)
q)q:([]time:2026.01.01D10:01:00 2026.01.01D10:01:00 2026.01.01D10:01:00 2026.01.01D10:01:02;sym:`g#`ibm`msft`msft`ibm;px:100 99 101 98)
q)T:.gpu.to t
q)Q:.gpu.to q
```

Join on a single column - every row in `t` is matched against the most recent row overall in `q`:
```q
q).gpu.aj[`time;T;Q]
time                          sym  qty px
------------------------------------------
2026.01.01D10:01:01.000000000 msft 100 101
2026.01.01D10:01:03.000000000 ibm  200 98
2026.01.01D10:01:04.000000000 ibm  150 98
```

Join on two columns - each row in `t` is matched against the most recent row in `q` _with the same `sym`_.
For two-column joins, the last key column must be sorted ascending within each exact-match key in `t2`.

The exact-match column may have the parted (`` `p ``) or grouped (`` `g ``) attribute, or no attribute:

- With `` `p ``, equal keys must occupy contiguous runs. `.gpu.to` builds an index from these runs without sorting or storing explicit row IDs.
- With `` `g ``, `.gpu.to` builds a grouped index.
- Without either attribute, each join builds and releases a temporary sorted index.

Both attributed indexes are retained with the GPU-resident column and reused by subsequent joins. To benefit from reuse, transfer `t2` once and keep the resulting GPU-resident table.
```q
q).gpu.aj[`sym`time;T;Q]
time                          sym  qty px
------------------------------------------
2026.01.01D10:01:01.000000000 msft 100 101
2026.01.01D10:01:03.000000000 ibm  200 98
2026.01.01D10:01:04.000000000 ge   150
```

The `ge` row has a null `px` because there is no `ge` in `q`.

To use a parted exact-match column, first arrange the RHS by key and time, then apply `` `p# `` before transferring it:
```q
q)qp:update `p#sym from (`sym`time xasc q)
q)QP:.gpu.to qp
q).gpu.attr exec sym from QP
`p
q)(aj[`sym`time;t;qp])~.gpu.from .gpu.aj[`sym`time;T;QP]
1b
```

The same index is reused by subsequent calls using `QP`, including `.gpu.aj0`, `.gpu.ajf`, and `.gpu.ajf0`.

### `aj` vs. `aj0`

`aj` and `aj0` return different times in their results:
```txt
aj    boundary time from t1
aj0   actual time from t2
```
```q
q).gpu.aj0[`sym`time;T;Q]
time                          sym  qty px
------------------------------------------
2026.01.01D10:01:00.000000000 msft 100 101
2026.01.01D10:01:02.000000000 ibm  200 98
2026.01.01D10:01:04.000000000 ge   150
```

### `ajf`, `ajf0`

`ajf` and `ajf0` fill from `t1` if the corresponding value in `t2` is null.
```q
q)t0:([]time:2#2026.01.01D00:00:01;sym:`a`b;p:1 1;n:`r`s)
q)t1:([]time:2#2026.01.01D00:00:01;sym:`a`b;p:0 1)
q)t2:([]time:2#2026.01.01D00:00:00;sym:`g#`a`b;p:1 0N;n:`r`s)
q)T1:.gpu.to t1
q)T2:.gpu.to t2
```

With `aj`, the null `p` value for `b` in `t2` is preserved in the result:
```q
q).gpu.aj[`sym`time;T1;T2]
time                          sym p n
-------------------------------------
2026.01.01D00:00:01.000000000 a   1 r
2026.01.01D00:00:01.000000000 b     s
```

With `ajf`, null values in `t2` are filled from `t1`:
```q
q).gpu.ajf[`sym`time;T1;T2]
time                          sym p n
-------------------------------------
2026.01.01D00:00:01.000000000 a   1 r
2026.01.01D00:00:01.000000000 b   1 s
q)t0 ~ .gpu.from .gpu.ajf[`sym`time;T1;T2]
1b
```

`ajf0` combines both: fills nulls from `t1` and uses the actual time from `t2`:
```q
q).gpu.ajf0[`sym`time;T1;T2]
time                          sym p n
-------------------------------------
2026.01.01D00:00:00.000000000 a   1 r
2026.01.01D00:00:00.000000000 b   1 s
```

## append

_Append two lists_
```syntax
.gpu.append[x;y]
.gpu.append[`name;y]
```

Where `x` and `y` are CPU or GPU-resident lists, dictionaries or tables, returns `x` joined to `y`.

Where `x` is a symbol atom, it names a global instead: `y` is appended to it and the symbol returned.
Refer to [appending by name](#appending-by-name).

> **Warning — Tensors**
>
> `.gpu.append` does not accept tensors and signals `type`. Join tensors with [`.gpu.tensor.concat`](#concatenating-tensors).
>
```q
q)A:.gpu.to a:til 5
q)B:.gpu.to b:a+10
q).gpu.append[A;b]
0 1 2 3 4 10 11 12 13 14

q)D:.gpu.to d:`a`b!(a;b)
q).gpu.append[D;d]
a| 0  1  2  3  4  0  1  2  3  4
b| 10 11 12 13 14 10 11 12 13 14

q)T:.gpu.to t:2#flip d
q)t
a b
----
0 10
1 11
q).gpu.append[T;t]
a b
----
0 10
1 11
0 10
1 11

```

> **Note — Only the `` `g`` attribute is preserved on GPU-resident appends.**
>
>
> Extending a parted column clears its `` `p `` attribute and releases its parted index, including when the append extends the column in place.
>
`.gpu.append` falls back to CPU join given entirely CPU-resident arguments.
```q
q).gpu.append[a;b]
0 1 2 3 4 10 11 12 13 14
q).gpu.append[a;b]
0 1 2 3 4 10 11 12 13 14
```

### Appending by name

Naming the target lets `.gpu.append` extend it in place instead of allocating a new vector and copying both inputs: the name proves the binding holds the only reference. Repeated appends to a growing global avoid the allocation and copying that `A:.gpu.append[A;y]` repeats each time.
```q
q)A:.gpu.to a:til 5
q).gpu.append[`A;5 6 7]
`A
q)A
0 1 2 3 4 5 6 7

q)T:.gpu.to t:([]a:1 2;b:10 20)
q).gpu.append[`T;([]a:enlist 3;b:enlist 30)]
`T
q)T
a b
----
1 10
2 20
3 30
```

Extending in place needs spare capacity, so it applies only under the `` `buddy `` allocation strategy. Refer to the `alloc` setting of [`.gpu.config`](#config).

> **Note — `.gpu.append` copies when anything else refers to the target, as `` `t upsert y `` does.**
>
>
> Other holders keep the old value and only the name is rebound, so the result is the same either way; only the cost differs. `.gpu.append` decides per column, so sharing one column does not stop the others extending in place.
>
> ```q
> q)A:.gpu.to a:til 5
> q)C:A                    / a second reference
> q).gpu.append[`A;5 6 7]
> `A
> q)A
> 0 1 2 3 4 5 6 7
> q)C            / unchanged
> 0 1 2 3 4
> ```
>
A failed append, for example on a type mismatch, leaves the global unchanged.

> **Warning — A symbol atom names a global, so appending a literal symbol needs it enlisted.**
>
>
> ```q
> q)S:.gpu.to `a`b`c
> q).gpu.append[enlist`abc;S]
> `abc`a`b`c
> ```
>
## asc

_Ascending sort_
```syntax
.gpu.asc x
```

Where `x` is a GPU-resident vector, returns its items in ascending order of value, with the `` `s `` attribute set, indicating the list is sorted.

If `x` already has the `` `s `` attribute, `.gpu.asc` returns `x` unchanged without sorting it.


> **Warning — Sorting on GPU-resident symbols is not yet implemented; consider an enumeration instead.**
>
>
> The exception is a symbol vector that already has the `` `s `` attribute: `.gpu.asc` returns it without comparing any items.
>
```q
q)A:.gpu.to a:7 4 7 1 0 5 9 9 2 9
q)a
7 4 7 1 0 5 9 9 2 9
q).gpu.attr .gpu.asc A
`s
q).gpu.asc A
`s#0 1 2 4 5 7 7 9 9 9
```

### iasc

_Ascending grade_
```syntax
.gpu.iasc x
```

Where `x` is a GPU-resident vector, returns the indices needed to sort `x` in ascending order.

If `x` already has the `` `s `` attribute, `.gpu.iasc` returns `til count x` without sorting.
```q
q)A:.gpu.to a:7 4 7 1 0 5 9 9 2 9
q)a
7 4 7 1 0 5 9 9 2 9
q).gpu.iasc A
4 3 8 1 5 0 2 6 7 9
q)a .gpu.from .gpu.iasc A
0 1 2 4 5 7 7 9 9 9

```

### idesc

_Descending grade_
```syntax
.gpu.idesc x
```

Where `x` is a GPU-resident vector or table, returns the indices needed to sort `x` in descending order. As in q, the grade is stable: equal values keep their original order, and nulls sort last.
```q
q)A:.gpu.to a:7 4 7 1 0 5 9 9 2 9
q)a
7 4 7 1 0 5 9 9 2 9
q).gpu.from .gpu.idesc A
6 7 9 0 2 5 1 8 3 4
q)a .gpu.from .gpu.idesc A
9 9 9 7 7 5 4 2 1 0
```

### xasc

_Sort a table in ascending order of specified columns_
```syntax
.gpu.xasc[x;y]
```

Where `x` is a symbol vector of column names defined in table `y`, returns `y` sorted in ascending order by `x`.
The sort is by the first column specified, then by the second column within the first, and so on.
All columns referenced in `x` must be GPU-resident.

If `x` names a single column that already has the `` `s `` attribute, `.gpu.xasc` returns `y` unchanged without sorting.


```q
q)T:.gpu.to t:0N?([]sym:raze 2#/:`a`b`c; date:6#2025.01.01+til 2; val:50+6?10f)
q)t
sym date       val
-----------------------
c   2025.01.02 51.1578
b   2025.01.01 57.52659
b   2025.01.02 59.49526
a   2025.01.01 59.74317
c   2025.01.01 53.74586
a   2025.01.02 50.81106
q).gpu.xasc[`date;T]
sym date       val     
-----------------------
b   2025.01.01 57.52659
a   2025.01.01 59.74317
c   2025.01.01 53.74586
c   2025.01.02 51.1578 
b   2025.01.02 59.49526
a   2025.01.02 50.81106
q).gpu.xasc[`date`val;T]
sym date       val     
-----------------------
c   2025.01.01 53.74586
b   2025.01.01 57.52659
a   2025.01.01 59.74317
a   2025.01.02 50.81106
c   2025.01.02 51.1578 
b   2025.01.02 59.49526

q)/ Use a symbol enum to sort on symbols
q).gpu.xasc[`sym`date;T]
'GPU_SYMBOL_SORTING_NOT_IMPLEMENTED
q)syms:exec distinct sym from t
q)T:.gpu.to t:update `syms$sym from t
q).gpu.xasc[`sym`date;T]
sym date       val     
-----------------------
a   2025.01.01 59.74317
a   2025.01.02 50.81106
b   2025.01.01 57.52659
b   2025.01.02 59.49526
c   2025.01.01 53.74586
c   2025.01.02 51.1578

```

### xdesc

_Sort a table in descending order of specified columns_
```syntax
.gpu.xdesc[x;y]
```

Where `x` is a symbol vector of column names defined in table `y`, returns `y` sorted in descending order by `x`. The sort is by the first column specified, then by the second column within the first, and so on. Equal keys keep their original row order, and nulls sort last, as in q's `xdesc`. All columns referenced in `x` must be GPU-resident. The result does not carry `` `s ``.
```q
q)T:.gpu.to t:([]sym:`a`b`a`c; date:2025.01.01 2025.01.02 2025.01.02 2025.01.01; val:1 2 3 4f)
q).gpu.from .gpu.xdesc[`date;T]
sym date       val
------------------
b   2025.01.02 2
a   2025.01.02 3
a   2025.01.01 1
c   2025.01.01 4
```

## attr

_Attributes of a GPU or CPU object_
```syntax
.gpu.attr x
```

Where `x` is any CPU or GPU-resident object, returns its attribute as a symbol atom.

The possible attributes are:

code    | attribute
:------:|-----------
`` `s`` | sorted
`` `u`` | unique
`` `p`` | parted
`` `g`` | grouped

A null symbol result `` ` `` means no attributes are set on `x`.

> **Note — All attributes are preserved on `.gpu.to`, while all but `` `s `` are lost on `.gpu.from`.**
>
>
> **Warning — Vanilla `attr` returns `` ` `` for GPU-resident virtual objects regardless of their true attributes.**
>
>
```q
q)G:.gpu.to g:`g#9 9 2 1 5 0 9 9 6 1
q)g
`g#9 9 2 1 5 0 9 9 6 1
q)attr g
`g
q)attr G        / vanilla attr insufficient on gpu virtual objects
`
q).gpu.attr G
`g
q).gpu.attr g   / .gpu.attr falls back to vanilla for CPU-resident objects
`g

q).gpu.attr .gpu.to til 10
`
q).gpu.attr .gpu.asc .gpu.to til 10
`s

q)attr .gpu.from .gpu.to `s#1 2 3
`s
q)attr .gpu.from .gpu.to `p#1 2 3
`               / only `s preserved on .gpu.from
```

## bin

_Binary search_
```syntax
.gpu.bin[x;y]
```

### Vectors

Where:

- `x` is a sorted GPU-resident vector.
- `y` is a GPU-resident vector of the same type.

returns the index of the _last_ item in `x` which is ≤ each `y[i]`. The result is `-1` where `y[i]` is less than the first item of `x`.

> **Note — Ordered-column types**
>
> `.gpu.aj` and `.gpu.bin` support long, timestamp, timespan, int, month, date, minute, second, and time ordered columns.
>
```q
q)A:.gpu.to a:0 2 4 6 8 10
q)B:.gpu.to b:5 6 7
q).gpu.bin[A;B]
2 3 3

q)B:.gpu.to b:-10 0 4 5 6 20
q).gpu.bin[A;B]
-1 0 2 2 3 5
```

`.gpu.bin` uses a binary search algorithm, which is generally more efficient on large data. The items of `x` must be sorted ascending although `.gpu.bin` does not verify this property.

### Tables

Where:

- `x` is a GPU-resident table of `n` columns
- `y` is a GPU-resident table with the same schema

returns the indices of the last row of `x` for which

- the first `n-1` columns each match the first `n-1` columns of the corresponding row of `y`, and
- the last column is not greater than the last column of the corresponding row of `y`.

If no items match the criteria, either because there are no rows that match in the first `n-1` columns, or because the last value is smaller than the last value in the first such row, `0N` is returned.

> **Warning — `.gpu.aj` and `.gpu.bin` are limited to one or two columns.**
>
> **Note — Indexes for two-column searches**
>
>
> `.gpu.bin` reuses the index built by `.gpu.to` when the exact-match column
> of `x` has the parted (`` `p ``) or grouped (`` `g ``) attribute.
> A parted index is built from contiguous key runs without sorting or storing
> explicit row IDs. Without a reusable index, each call builds and releases a
> temporary sorted index. Refer to [as-of joins](#aj) for attribute and ordering
> requirements.
>
> **Note — Enum exact-match columns**
>
>
> An enum exact-match column is compared by symbol, so `x` and `y` may use different enum domains. The
> index of `x` is used as it is: the symbols of `y` are translated into the domain of `x`, and a symbol that the
> domain of `x` does not contain gives `0N`.
>
```q
q)A:.gpu.to a:([]a:`p#`p`p`p`q`q`q;b:0 2 4 0 2 4)
q)B:.gpu.to b:([]a:`q;b:-1 1 3 5)
q).gpu.bin[A;B]
0N 3 4 5
```

To use `bin` with a table, the last column need not be sorted overall, but it needs to be sorted within the equivalence classes defined by the first `n-1` columns (as shown in the previous example).

## cov

_Covariance_
```syntax
.gpu.cov[x;y]
```

Where `x` and `y` are flat, same-length, GPU-resident float vectors on the currently selected device, returns a one-element GPU-resident float vector containing their population covariance.

Rows where either `x` or `y` is null are excluded as complete pairs. The covariance is calculated over the remaining valid pairs.

> **Warning — Inputs must be flat float vectors on the current device.**
>
> CPU-resident inputs, nested lists, matrices, and other q types are not supported. Both vectors must reside on the GPU currently selected by [`.gpu.setDev`](#setdev).
>
> **Note**
>
> The result is a one-element GPU-resident vector rather than a q float atom. Use `first .gpu.from result` to obtain the corresponding q atom.
>
```q
q)x:1 2 3 4f
q)y:2 4 6 8f
q)X:.gpu.to x
q)Y:.gpu.to y

q)R:.gpu.cov[X;Y]
q)R
,2.5
q)R
,2.5
q)first .gpu.from R
2.5
```

Nulls and their corresponding pairs are excluded:
```q
q)X:.gpu.to 1 2 0n 4f
q)Y:.gpu.to 2 4 6 8f
q)first .gpu.from .gpu.cov[X;Y]
3.111111
```

The result is null when there are no valid pairs. Empty inputs also return a one-element float-null vector.
```q
q)first .gpu.from .gpu.cov[.gpu.to `float$();.gpu.to `float$()]
0n
```

> **Info**
>
> Floating-point results may differ slightly from CPU `cov` because the GPU reduction can combine values in a different order.
>
## scov

_Sample covariance_
```syntax
.gpu.scov[x;y]
```

Where `x` and `y` are flat, same-length, GPU-resident float vectors on the currently selected device, returns a one-element GPU-resident float vector containing their sample covariance.

For `n` valid pairs, sample covariance is calculated from population covariance as:
```txt
scov = cov × n ÷ (n - 1)
```

Rows where either input is null are excluded as complete pairs, and `n` is the number of remaining valid pairs.

The input and output restrictions are the same as for [`.gpu.cov`](#cov).
```q
q)X:.gpu.to 1 2 3 4f
q)Y:.gpu.to 2 4 6 8f
q)first .gpu.from .gpu.scov[X;Y]
3.333333
```

The correction factor uses the number of valid pairs:
```q
q)X:.gpu.to 1 2 0n 4f
q)Y:.gpu.to 2 4 6 8f
q)first .gpu.from .gpu.scov[X;Y]
4.666667
```

Sample covariance is null when fewer than two valid pairs remain:
```q
q)first .gpu.from .gpu.scov[.gpu.to enlist 3f;.gpu.to enlist 9f]
0n

q)first .gpu.from .gpu.scov[.gpu.to 0n 2 0n;.gpu.to 1 4 0n]
0n
```

> **Info**
>
> Floating-point results may differ slightly from CPU `scov` because the GPU reduction can combine values in a different order.
>
## cor

_Correlation coefficient_
```syntax
.gpu.cor[x;y]
```

Where `x` and `y` are flat, same-length, GPU-resident float vectors on the currently selected device, returns a one-element GPU-resident float vector containing their Pearson correlation coefficient.

Rows where either input is null are excluded as complete pairs. The input and output restrictions are the same as for [`.gpu.cov`](#cov).

The result is between `-1f` and `1f` for finite, non-constant inputs:
```q
q)X:.gpu.to 1 2 3 4f
q)Y:.gpu.to 2 4 6 8f
q)first .gpu.from .gpu.cor[X;Y]
1f

q)Y:.gpu.to 8 6 4 2f
q)first .gpu.from .gpu.cor[X;Y]
-1f
```

Nulls and their corresponding pairs are excluded:
```q
q)X:.gpu.to 1 2 0n 4f
q)Y:.gpu.to 2 4 6 8f
q)first .gpu.from .gpu.cor[X;Y]
1f
```

Correlation is null if either input is constant across the valid pairs, if fewer than two valid pairs remain, or if no valid pairs remain:
```q
q)X:.gpu.to 3 3 3f
q)Y:.gpu.to 1 2 3f
q)first .gpu.from .gpu.cor[X;Y]
0n
```

> **Info**
>
> Floating-point results may differ slightly from CPU `cor` because the GPU reduction can combine values in a different order.
>
## lj

_Left join_
```syntax
.gpu.lj[left;right]
```

Where:

- `left` is a CPU, GPU, or mixed-residency table
- `right` is a CPU, GPU, or mixed-residency keyed table

returns a mixed-residency table equivalent to `left lj right` for supported inputs.

The result preserves columns from `left`. Columns copied only from `left` keep their original residency. Columns gathered from `right` keep the residency of the corresponding RHS columns. Therefore, a mixed-residency RHS table can produce a mixed-residency result. Use `.gpu.from` to materialize any GPU-resident result columns on the CPU.
```q
q)left:([]id:1 2 3 4j; qty:10 20 30 40)
q)right:`id xkey ([]id:1 3j; px:100 300)
q).gpu.lj[.gpu.to left;.gpu.to right]
id qty px
----------
1  10  100
2  20
3  30  300
4  40
```

Multiple RHS key columns are supported.
```q
q)left:([]sym:`a`a`b`b; id:1 2 1 2j; qty:10 20 30 40)
q)right:`sym`id xkey ([]sym:`a`b; id:2 1j; px:200 300)
q).gpu.lj[.gpu.to left;.gpu.to right]
sym id qty px
--------------
a   1  10
a   2  20  200
b   1  30  300
b   2  40
```

Supported key types are integral types, integer-backed temporal types, symbols, and enums with matching domains. `int` and `long` key columns may be joined with each other. RHS value columns must be simple, non-nested vectors supported by GPU gather.

`.gpu.lj` returns `nyi` for unsupported inputs. These include non-keyed RHS tables, RHS key columns missing from `left`, float-backed key types, datetime keys, nested result columns, and enum keys with different domains.

> **Note — Index reuse**
>
>
> With a single key column, `.gpu.lj` probes the persistent index that [`.gpu.to`](#to) builds when the RHS key column has the parted (`` `p ``), grouped (`` `g ``) or unique (`` `u ``) attribute, instead of building a hash table over every RHS row on each call. To benefit, transfer the keyed RHS once and keep the GPU-resident table. Joins with more than one key column, `int`/`long` key pairs, and RHS key columns without any of these attributes, or with no rows, build the hash table on each call. To compare the two paths, set the environment variable `GPULIB_JOIN_INDEX` to `hash`, which forces the hash table; other non-empty values are reported on stderr and ignored.
>
> **Warning**
>
>
> If `right` contains duplicate keys and `.gpu.lj` uses the persistent index, it returns the first matching RHS row, as q `lj` does. Otherwise it selects any matching RHS row, which may differ from the row q `lj` returns.
>
## ij

_Inner join_
```syntax
.gpu.ij[left;right]
```

Where:

- `left` is a CPU, GPU, or mixed-residency table
- `right` is a CPU, GPU, or mixed-residency keyed table

returns a mixed-residency table equivalent to `left ij right` for supported inputs.

Only rows from `left` whose key values match a row in `right` are included. The order of matching rows from `left` is preserved.

The result contains all columns from `left`, followed by columns from the RHS value table that do not already exist in `left`. When a non-key column exists on both sides, its result values come from `right`.

Result columns gathered from `left` have the same residency as their corresponding LHS columns. Columns gathered from `right` have the same residency as their corresponding RHS columns. Therefore, CPU, GPU, or mixed-residency inputs can produce a mixed-residency result. Use `.gpu.from` to materialize any GPU-resident result columns on the CPU.
```q
q)left:([]id:1 2 3 4j; qty:10 20 30 40)
q)right:`id xkey ([]id:1 3j; px:100 300)
q).gpu.ij[.gpu.to left;.gpu.to right]
id qty px
----------
1  10  100
3  30  300
```

Multiple RHS key columns are supported.
```q
q)left:([]sym:`a`a`b`b; id:1 2 1 2j; qty:10 20 30 40)
q)right:`sym`id xkey ([]sym:`a`b; id:2 1j; px:200 300)
q).gpu.ij[.gpu.to left;.gpu.to right]
sym id qty px
--------------
a   2  20  200
b   1  30  300
```

Supported key types are integral types, integer-backed temporal types, symbols, and enums with matching domains. `int` and `long` key columns may be joined with each other. Result columns must be simple, non-nested vectors supported by gather.

`.gpu.ij` returns `nyi` for unsupported inputs. These include non-keyed RHS tables, RHS key columns missing from `left`, float-backed key types, datetime keys, nested result columns, and enum keys with different domains.

> **Note — Index reuse**
>
>
> With a single key column, `.gpu.ij` probes the persistent index that [`.gpu.to`](#to) builds
> when the RHS key column has the parted (`` `p ``), grouped (`` `g ``) or unique (`` `u ``)
> attribute, instead of building a hash table over every RHS row on each call. To benefit,
> transfer the keyed RHS once and keep the GPU-resident table. Joins with more than one key
> column, `int`/`long` key pairs, and RHS key columns without any of these attributes, or with no
> rows, build the hash table on each call. To compare the two paths, set the environment variable
> `GPULIB_JOIN_INDEX` to `hash`, which forces the hash table; other non-empty values are reported
> on stderr and ignored.
>
> **Warning**
>
>
> If `right` contains duplicate keys and `.gpu.ij` uses the persistent index, it returns the first
> matching RHS row, as q `ij` does. Otherwise it selects any matching RHS row, which may differ
> from the row q `ij` returns.
>
## wj

_Window join with prevailing value_
```syntax
.gpu.wj[windows;commonColumns;left;rightAndAggregations]
```

`.gpu.wj` is the GPU implementation of q `wj`. Its arguments and window semantics match q `wj` for supported inputs.

The result preserves the rows and columns of `left` and appends one GPU-resident column for each aggregation. Use `.gpu.from` to materialize the result on the CPU.
```q
q)trades:([]sym:`ibm`ibm;
    time:10:00:05 10:00:10;
    price:100 101f)

q)quotes:update `p#sym from
    ([]sym:5#`ibm;
      time:10:00:02 10:00:04 10:00:06 10:00:09 10:00:11;
      ask:101 102 103 104 105;
      bid:99 100 101 102 103)

q)windows:-2 1+\:trades`time

q).gpu.wj[
    .gpu.to windows;
    `sym`time;
    .gpu.to trades;
    (.gpu.to quotes;(max;`ask);(min;`bid))]
sym time     price ask bid
--------------------------
ibm 10:00:05 100   103 99
ibm 10:00:10 101   105 101
```

GPU-specific support and limitations:

- `windows` may be CPU-resident, contain individually GPU-resident vectors (`.gpu.to each windows`), or be transferred as a nested GPU object (`.gpu.to windows`).
- `commonColumns` contains zero or more equality-key columns followed by one temporal column. For a time-only join, use ``enlist `time``.
- Equality keys support boolean, byte, char, short, int, long, symbol, enum, timestamp, month, date, timespan, minute, second, and time. Corresponding LHS and RHS types must match exactly, including enum domains. Any number of equality keys is supported.
- The temporal column supports timestamp, month, date, timespan, minute, second, and time.
- Supported aggregations and RHS input types are:

 **Aggregations**           | **RHS input types**
 ---------------------------|---
 `sum`, `min`, `max`, `avg` | boolean, byte, char, short, int, long, real, float, timestamp, month, date, timespan, minute, second, time
 `count`, `first`, `last`   | the preceding types, plus GUID, symbol, and enum

Aggregation result types follow q.

The following columns must be GPU-resident:

- on the LHS, the equality-key and temporal columns;
- on the RHS, the equality-key, temporal, and aggregate columns.

Other LHS columns may be CPU- or GPU-resident and are returned unchanged. Nested aggregate columns and unlisted operations or input types are not supported.

> **Warning**
>
>
> When equality keys are supplied, the first RHS equality-key column must have the parted attribute (`p#`). RHS rows must be ordered by all equality keys in `commonColumns` order, followed by ascending temporal value within each complete key tuple.
>
> For a time-only join, the RHS must be globally ordered by ascending temporal value. `.gpu.wj` does not sort the RHS or validate temporal ordering.
>
> **Note — Index reuse**
>
>
> With a single equality key, `.gpu.wj` and `.gpu.wj1` reuse the parted index that
> [`.gpu.to`](#to) builds for the RHS key column, instead of grouping the RHS on every call.
> To benefit, transfer the RHS once and keep the GPU-resident table:
>
> ```q
> q)Q:.gpu.to quotes
> q).gpu.from .gpu.wj[windows;`sym`time;.gpu.to trades;(Q;(max;`ask);(min;`bid))]
> ```
>
> Joins with more than one equality key group the RHS on every call.
> Appending to the RHS with [`.gpu.append`](#append) clears its `` `p `` attribute and index,
> after which `.gpu.wj` rejects the table until `` `p# `` is reapplied on the CPU and the RHS is
> transferred again.
>
> To compare the two paths, set the environment variable `GPULIB_WJ_GROUPING` to `sort`, which
> groups the RHS on every call even when the index is available; other non-empty values are
> reported on stderr and ignored.
>
## wj1

_Window join without prevailing value_
```syntax
.gpu.wj1[windows;commonColumns;left;rightAndAggregations]
```

`.gpu.wj1` is the GPU implementation of q `wj1`. It has the same GPU-specific support, residency requirements, and ordering requirements as `.gpu.wj`.

> **Note**
>
>
> Unlike q `wj1`, `.gpu.wj1` treats an inverted window as an empty range instead of raising a `domain` error.
>
## compact

_Reclaim the capacity reserved by GPU-resident vectors_
```syntax
.gpu.compact x
```

Where `x` is a GPU-resident vector, dictionary, table, keyed table, or any list of them, reallocates
the data of every GPU-resident vector it holds to exactly the bytes that vector uses, and returns `x`.
Anything inside `x` that is not GPU-resident is left as it is, so you can compact a mixed-residency table.

`x` itself must be GPU-resident or a shape that can hold something that is, otherwise `.gpu.compact` signals `type`. A tensor is accepted and left as it is, alone or inside a list or dictionary: it holds no reserved capacity, and its storage is shared with its views. `.gpu.compact` does not accept a symbol atom as the name of a global, as [`.gpu.append`](#append) does.
It updates the object in place, so it does not need the name.

`.gpu.compact` reallocates data that other references can still point to, so it runs only on the
main thread. From any other thread it signals `` `noupdate ``, as q does for a global amend from a
secondary thread. This includes calls inside [`peach`](https://code.kx.com/q/ref/each/#peach), and
client queries in a process started with a multi-threaded input queue (a negative `-s`), which do not
run on the main thread.

Under the default `` `buddy `` allocation strategy, a vector reserves up to twice the memory its data
needs, so that [`.gpu.append`](#append) can extend it in place.
When a table stops growing, call `.gpu.compact` to release the reserved memory.
In the following example, each column needs 3 MiB plus 8 bytes, so `` `buddy `` reserves 4 MiB per
column. After compaction, the two columns use 6,291,472 bytes.
```q
q).gpu.config ([alloc:`buddy])
q)n:1+3*1024*1024 div 8   / 3MB + 1 element per column
q)T:.gpu.to ([]a:n?0j; b:n?0j)

q).gpu.sync[]; .gpu.memDev[]`used
8388608
q).gpu.compact T;
q).gpu.sync[]; .gpu.memDev[]`used
6291472
```

> **Note — Only column data is compacted.**
>
>
> Index allocations associated with grouped (`` `g ``), parted (`` `p ``) and
> unique (`` `u ``) attributes, and the offsets behind nested columns, retain
> their allocated capacity.
>
`.gpu.compact` allocates the new copy before it releases the original, so compacting a column needs
free device memory equal to the size of that column. If that memory is not available, `.gpu.compact`
copies through host memory instead. This path is slower, releases the original first, and counts
against the `-w` memory limit of the process.

## config

_Get or set the runtime configuration_
```syntax
.gpu.config[]
.gpu.config d
```

Where `d` is a symbol-keyed dictionary of settings to apply, returns nothing.
Called with no argument, returns every setting as a dictionary.
```q
q).gpu.config[]
memRelThres| 4294967296
alloc      | `buddy
q).gpu.config ([memRelThres:20*1024*1024*1024;alloc:`tight])
q).gpu.config[]
memRelThres| 21474836480
alloc      | `tight
```

Applying a dictionary is all-or-nothing: every key and value is validated first, so a setting that is not valid
leaves the configuration unchanged.
An unrecognized key, or an out-of-range value for a recognized key, signals `` `domain ``.
A value of the wrong type signals `` `type ``.
```q
q).gpu.config ([alloc:`asdf])
'domain
q).gpu.config ([asdf:`buddy])
'domain
```

### Settings

**Setting**   | **Type** | **Default**  | **Scope**  | **Description**
--------------|----------|--------------|------------|----------------------------------------------
`memRelThres` | long     | `4294967296` | device     | Memory release threshold in bytes.
`alloc`       | symbol   | `` `buddy `` | global     | Allocation strategy for GPU-resident vectors.

`memRelThres` is a property of the memory pool of the active device, so it is read and written for
whichever device [`.gpu.setDev`](#setdev) has selected, and each device carries its own value.
When the pool has reserved more than `memRelThres` bytes, it releases unused memory back down to the
threshold at synchronization points, such as [`.gpu.sync`](#sync) or [`.gpu.from`](#from). It does
not limit how much memory can be allocated. The `peak` field of [`.gpu.memDev`](#memdev) is the peak
reserved memory, and can be used for sizing.

> **Note — Setting `memRelThres` can be a significant performance win and is recommended.**
>
>
You can set a `global`-scope setting only from the main thread. From any other thread,
`.gpu.config` signals `` `noupdate ``, as [`.gpu.compact`](#compact) does.
You can read settings from any thread.

`alloc` is global and controls how much memory a GPU-resident vector reserves:

- `` `buddy `` rounds each allocation up to a power of two, as KDB-X does on the host. The spare
  capacity lets [`.gpu.append`](#append) extend a vector in place.
- `` `tight `` allocates exactly the bytes the data needs, which can save up to half the VRAM of a
  column. [`.gpu.append`](#append) can never extend in place, so every append allocates a new
  vector and copies both inputs into it.

> **Note — `` `buddy `` is necessary but not sufficient for an in-place append.**
>
>
> The target must also be provably unshared. Refer to
> [appending by name](#appending-by-name).
>
Use `` `tight `` when a dataset has to fit in VRAM and is not being appended to; keep the default
`` `buddy `` for workloads that append.
Changing `alloc` affects allocations made from that point on; vectors already on the device keep the
capacity they were created with.
```q
q).gpu.config ([memRelThres:0])
q)n:1+3*1024*1024 div 8   / 3MB + 1 element
q)used:{.gpu.sync[]; b:.gpu.memDev[]`used; c:.gpu.to x?0j; .gpu.sync[]; .gpu.memDev[][`used]-b}

q).gpu.config ([alloc:`buddy])
q)used n
4194304

q).gpu.config ([alloc:`tight])
q)used n
3145736
```

## count

_Count of a CPU or GPU-resident object_
```syntax
.gpu.count x
```

Where `x` is:

- a vector, returns the number of elements
- a dictionary, returns the number of keys
- a table, returns the number of rows
- anything else, `1`

> **Warning — Vanilla `count` returns `1` for GPU-resident virtual objects regardless of their true size.**
>
>
```q
q)A:.gpu.to a:til 5
q)D:.gpu.to d:`a`b!(a;a+10)
q)T:.gpu.to t:flip d

q).gpu.count each (a;d;t)
5 2 5
q).gpu.count each (A;D;T)
5 2 5

q)count each (a;d;t)
5 2 5
q)count each (A;D;T)
1 1 1
```

## drop

_Drop specified count, keys or columns_
```syntax
.gpu.drop[x;y]
```

Where `x` is a symbol atom or vector, returns a dictionary/table `y` without the specified keys/columns.


> **Note**
>
> Unlike the standard `drop`, `.gpu.drop` does not support dropping by count; it only supports dropping by key or column name.
>
```q
q)T:.gpu.to t:([]a:`x`y`z;b:0 2 4;c:1 2 3)
q)T
a b c
-----
x 0 1
y 2 2
z 4 3
q).gpu.drop[`b] T
a c
---
x 1
y 2
z 3
q).gpu.drop[`b] t
a c
---
x 1
y 2
z 3
q).gpu.drop[`a`b] t
c
-
1
2
3
q).gpu.drop[`a`b] T
c
-
1
2
3
```

## from

_Retrieve data from the GPU_
```syntax
.gpu.from x
```

Where `x` is any CPU, GPU or mixed-resident object. Returns `x` moved to the CPU. A tensor, alone or inside a list or dictionary, comes back as [`.gpu.tensor.from`](#tensor) returns it; like `.gpu.tensor.from`, a tensor above rank 2 signals `nyi`.

> **Note — All attributes are preserved on `.gpu.to`, while all but `` `s `` are lost on `.gpu.from`.**
>
>
```q
q)A:.gpu.to a:til 5
q)D:.gpu.to d:`a`b!(a;a+10)
q)T:.gpu.to t:flip d

q).gpu.from A
0 1 2 3 4
q).gpu.from a
0 1 2 3 4
q).gpu.from D
a| 0  1  2  3  4
b| 10 11 12 13 14
q).gpu.from T
a b
----
0 10
1 11
2 12
3 13
4 14
```

## gather

_Gather specified indices of a list_
```syntax
.gpu.gather[x;y]
```

Where:

- `x` is a GPU-resident vector
- `y` is a short, int, or long vector of indices into `x`, either CPU-resident or GPU-resident.

returns the elements of `x` at each index `y`.
```q
q)A:.gpu.to a:7 4 7 1 0 5 9 9 2 9
q)a
7 4 7 1 0 5 9 9 2 9
q)B:.gpu.to b:2 4 6 8
q)a b
7 0 9 2
q).gpu.gather[A;B]
7 0 9 2

q).gpu.asc[A]
`s#0 1 2 4 5 7 7 9 9 9
q).gpu.gather[A] .gpu.iasc A
0 1 2 4 5 7 7 9 9 9
```

Null, negative, and out-of-range indices produce the source type's q null value in the output
(including NaN for real, float, and datetime, a space for char, and the empty symbol for symbol).
Boolean and byte vectors use zero, as in native q indexing.
```q
q)A:.gpu.to a:1 2 3
q).gpu.gather[A;.gpu.to 1 2 0N]
2 3 0N
```

## getDev

_Get current GPU_
```syntax
.gpu.getDev x
```

Where `x` is:

- null, returns the currently selected device index
- a GPU-resident object, returns the device index on which it resides
```q
q).gpu.setDev 0
q).gpu.getDev[]
0
q)A:.gpu.to 1 2 3
q).gpu.setDev 2
q).gpu.getDev[]
2
q).gpu.getDev A
0
```

## getMemRelThres

_Get the memory release threshold_
```syntax
.gpu.getMemRelThres[]
```

> **Warning — Deprecated: use [`.gpu.config`](#config) instead.**
>
>
> ``.gpu.getMemRelThres[]`` is equivalent to ``.gpu.config[]`memRelThres``.
>
Get the memory release threshold for the GPU memory pool of the active device.
Refer to the [`memRelThres` setting](#settings) of [`.gpu.config`](#config) for what the threshold does.
```q
q).gpu.getMemRelThres[]
4294967296
q).gpu.setMemRelThres[1024]
q).gpu.getMemRelThres[]
1024
q).gpu.setDev 2
q).gpu.getMemRelThres[]
4294967296
```

## group

_Apply GPU grouped attribute_
```syntax
.gpu.group x
```

Where `x` is a GPU-resident vector, returns `x` with the `` `g`` attribute set.

If `x` has the parted (`` `p ``) attribute, `.gpu.group` replaces its parted index with a grouped index and changes the attribute to `` `g ``. The same applies to columns passed to [`.gpu.xgroup`](#xgroup).

> **Warning — `.gpu.aj` and `.gpu.bin` order on long, timestamp, timespan, int, month, date, minute, second or time columns.**
>
> **Note — All attributes are preserved on `.gpu.to`, while all but `` `s `` are lost on `.gpu.from`.**
>
>
```q
q)A:.gpu.to a:7 4 7 1 0 5 9 9 2 9
q)a
7 4 7 1 0 5 9 9 2 9
q)G:.gpu.group A
q).gpu.attr G
`g
q).gpu.from G
7 4 7 1 0 5 9 9 2 9     / `g not preserved on .gpu.from
```

### xgroup

_Apply GPU grouped attribute to specific columns/keys_
```syntax
.gpu.xgroup[x;y]
```

Where `x` is a symbol atom/list specifying GPU-resident keys/columns of a dictionary/table `y`, returns `y` with specified columns/keys grouped.
```q
q)T:.gpu.to t:flip `a`b!(1 2 3 4;10 20 30 40)
q).gpu.meta T
c| t f a r
-| ---------
a| j     gpu
b| j     gpu
q).gpu.meta .gpu.xgroup[`a] T
c| t f a r
-| ---------
a| j   g gpu
b| j     gpu
```

### ungroup

_Flatten a table's list-valued columns into one row per element_
```syntax
.gpu.ungroup t
```

Where `t` is a GPU-resident table, returns `t` with every row's list-valued columns exploded into one row per
element; atom-valued columns are repeated once per exploded row. Matches native q's
[`ungroup`](https://code.kx.com/q/ref/ungroup/): every list-valued column in a row must have the
same count (else `` `length``), a row whose list columns have count 0 vanishes from the result, and
a table with no list-valued column at all raises `` `rank``.
```q
q)t:([] a:1 2 3; b:(1 2;0#0N;3 4 5))
q)ungroup t
a b
---
1 1
1 2
3 3
3 4
3 5
q).gpu.ungroup .gpu.to t
a b
---
1 1
1 2
3 3
3 4
3 5
q)t2:([] x:1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5; y:0 0 0 0 0 1 1 1 1 1)
q)ungroup select a:sums x by y from t2
y a   
------
0 1.5 
0 4   
0 7.5 
0 12  
0 17.5
1 6.5 
1 14  
1 22.5
1 32  
1 42.5
q).gpu.ungroup .gpu.select[.gpu.to t2;();enlist[`y]!enlist`y;enlist[`a]!enlist(sums;`x)]
y a   
------
0 1.5 
0 4   
0 7.5 
0 12  
0 17.5
1 6.5 
1 14  
1 22.5
1 32  
1 42.5
```

## memDev

_Get memory info for current GPU_
```syntax
.gpu.memDev[]
```

Returns memory utilization for the currently selected device in bytes, similar to `.Q.w[]`.
```q
q).gpu.memDev[]
used| 512
heap| 33554432
peak| 33554432
mphy| 23661248512
free| 23426301952

q)a:.gpu.memDev[]
q).gpu.memDev[]-a
used| 0
heap| 0
peak| 0
mphy| 0
free| 0
q)A:.gpu.to til 1000
q).gpu.memDev[]-a
used| 8192
heap| 0
peak| 0
mphy| 0
free| 0
q).gpu.setDev 1
q).gpu.memDev[]
used| 0
heap| 0
peak| 0
mphy| 23661248512
free| 23459856384
```

## meta

_Metadata of a table, including GPU-residency info_
```syntax
.gpu.meta x
```

Where `x` is a CPU, GPU or mixed-residency table, returns the metadata including a residency column.
q's own `meta` returns the same result for a GPU or mixed-residency table.
```q
q)syms:-10?`3
q)q:`time xasc([]time:2026.07.10D12:32+5?00:00:00;sym:5?syms;px:95+5?10)
q).gpu.meta q
c   | t f a r  
----| ---------
time| p   s cpu
sym | s     cpu
px  | j     cpu
q).gpu.meta .gpu.to q
c   | t f a r  
----| ---------
time| p   s gpu
sym | s     gpu
px  | j     gpu
q).gpu.meta .gpu.xto[`time`sym] q
c   | t f a r  
----| ---------
time| p   s gpu
sym | s     gpu
px  | j     cpu

q)/ q's meta gives the same result on a GPU table
q)meta .gpu.xto[`time`sym] q
c   | t f a r  
----| ---------
time| p   s gpu
sym | s     gpu
px  | j     cpu
```

## cntDev

_Count of available GPUs_
```syntax
.gpu.cntDev[]
```

Returns the number of available GPUs. A process may be limited by setting `CUDA_VISIBLE_DEVICES` in its environment.
```q
q).gpu.cntDev[]
4
q){.gpu.setDev x;.gpu.getDev[]} peach til 4
0 1 2 3

q) / in another process limited to devices 1 and 3
q)getenv `CUDA_VISIBLE_DEVICES
"1,3"
q).gpu.cntDev[]
2
q){.gpu.setDev x;.gpu.memDev[]} peach til .gpu.cntDev[]
used heap peak mphy        free
--------------------------------------
0    0    0    23661248512 23459856384
0    0    0    23661248512 23459856384
```

## nvtx

_Use the NVIDIA NVTX tool to record ranges that can be viewed in Nsight Systems._

### end {#nvtx-end}

_Mark the end of a record range_
```syntax
.gpu.nvtx.end[range_id]
```

Mark the end of the range with the id `range_id`.
Refer to [`.gpu.nvtx.start`](#nvtx-start).
```q
range_id:.gpu.nvtx.start["big select statement"]
.gpu.from .gpu.select[...]
.gpu.nvtx.end[range_id]
```

### start {#nvtx-start}

_Mark the beginning of a record range_
```syntax
range_id:.gpu.nvtx.start[desc_str]
```

Mark the beginning of the range with a descriptive string.
`.gpu.nvtx.start` returns a long id value that should be used with `.gpu.nvtx.end` to stop recording.
There are a few ways to use NVTX ranges with [Nsight Systems](https://docs.nvidia.com/nsight-systems/UserGuide/index.html) that can be helpful for finding bottlenecks.
```q
range_id:.gpu.nvtx.start["big select statement"]
.gpu.from .gpu.select[...]
.gpu.nvtx.end[range_id]
```

## profiler

_Access to the [CUDA profiler control API](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__PROFILER.html)_

Use it to selectively profile a specific section of your application.

### start {#profiler-start}

_Calls cudaProfilerStart_
```syntax
.gpu.profiler.start[]
```

### stop {#profiler-stop}

_Calls cudaProfilerStop_
```syntax
.gpu.profiler.stop[]
```

## setDev

_Set current GPU_
```syntax
.gpu.setDev x
```

Where `x` is a long from `0` to `.gpu.cntDev[]-1`, selects the device with that index.

> **Note — `.gpu.setDev` takes an index into the available device list. For a process with `CUDA_VISIBLE_DEVICES='3,1'`, `.gpu.setDev 0` selects the device with system ID 3.**
>
>
```q
q)getenv `CUDA_VISIBLE_DEVICES
"3,1"
q).gpu.getDev[]
0
q)A:.gpu.to til 1000
q).gpu.setDev 1
q).gpu.getDev[]
1
q)B:.gpu.to til 1000
q).gpu.getDev each (A;B)
0 1
```

> **Warning — `.gpu.setDev` selects the device for the calling host thread, and it stays selected there until the next `setDev`.**
>
> A branch of `peach` runs on a pool thread that q reuses, so the branch's device stays on that
> thread after the branch returns, and a nested `peach` branch starts on whatever device its own
> thread last used. Set the device in the branch that does the work, never rely on it carrying
> over to a later branch, and use [`.gpu.onDev`](#ondev) to scope a device to one call.
>
## onDev

_Run a call with one device current, restoring the previous one_
```syntax
.gpu.onDev[d;f]
```

Where `d` is a long from `0` to `.gpu.cntDev[]-1` and `f` is a function that takes one argument, makes device `d` current, calls `f[]`, and restores the device that was current before, whether `f` returns or signals. Returns `f`'s result.

`.gpu.setDev` is per host thread (see the warning there), so a `peach` branch that sets a device leaves its pool thread on it. `.gpu.onDev` scopes the device to the call: pool threads are never left on another device, and an error in the branch cannot leave one there either.
```q
q).gpu.getDev[]
0
q).gpu.onDev[1;{.gpu.getDev .gpu.to til 1000}]
1
q).gpu.getDev[]
0
```

`f` is called as `f[]`, so its one argument arrives as `::`. A lambda without an argument list, such as `{.gpu.to til 1000}`, takes the one argument `x`: `x` is `::` there, so `.gpu.onDev[d;{.gpu.to x}]` uploads `::` rather than signaling. Pass a branch's own values to `f` by projecting them, as q has no closures: `.gpu.onDev[d;{[t;x] ...}[t]]` leaves `x` to arrive as `::`. This is also how to create data on a chosen device without moving the calling thread's, for example in a nested `peach` branch that works on its outer branch's device: `.gpu.onDev[d;{[t;x] .gpu.to t}[t]]`.

`f` can be a lambda, a projection, a composition (which takes its rightmost function's arguments) or another `.gpu` function, as long as it leaves exactly one argument open, or the symbol naming one. `.gpu.onDev` checks `f` before it changes the device, so a refused `f` never runs and the device is unchanged:

- `'type`: `f` is not a function: a list, dictionary, atom, string or file handle, a q operator or primitive such as `+`, `neg` or `::`, or an iterator-derived function such as `+/` or `g'` (call it from a lambda instead), or a symbol that names none of these. An undefined symbol signals its own name.
- `'rank`: `f` leaves more than one argument open, such as `{[t;x] ...}` without its projection or `{[a;b;c] ...}[;1;]`. Without this check, `f[]` would return a projection without running `f`.

q shows an elided argument as `::`, so an argument projected as `::` also counts as open: `{[a;b;c] ...}[::;1]` is refused although only `c` is open. Make the call inside a lambda instead: `{g[::;1;3]}`.

A `.gpu.setDev` inside `f` holds until `f` returns, and `.gpu.onDev` then restores the device that was current at the call. `.gpu.onDev` calls nest: an inner call restores the outer call's device. An invalid `d` signals before `f` runs.

## select

_qSQL and functional select and exec on a GPU table_
```syntax
select ... from T ...
exec ... from T ...
?[T;c;b;a]
.gpu.select[T;c;b;a]
```

A GPU table works with q's own [qSQL](https://code.kx.com/q/basics/qsql/) `select` and `exec` (without `by`), and with the [functional form](https://code.kx.com/q/basics/funsql/) `?[t;c;b;a]`: the query runs on the GPU and returns a GPU table, which you can query again, join or sort without copying it back. Displaying a result at the console copies only the rows shown; use [`.gpu.from`](#from) to bring the whole result to the CPU.

### qSQL
```q
q)t:([]sym:`AAPL`MSFT`AAPL`MSFT`AAPL;p:150 300 152 298 155;size:100 200 150 300 250)
q)T:.gpu.to t

q)select from T where sym=`AAPL,p>151
sym  p   size
-------------
AAPL 152 150
AAPL 155 250

q)select p_min:min p,p_max:max p,p_avg:avg p by sym from T
sym | p_min p_max p_avg
----| --------------------
AAPL| 150   155   152.3333
MSFT| 298   300   299

q)select vwap:size wavg p by sym from T
sym | vwap
----| -----
AAPL| 153.1
MSFT| 298.8

q)select notional:p*size,lp:log p from T
notional lp
-----------------
15000    5.010635
60000    5.703782
22800    5.023881
89400    5.697093
38750    5.043425

q)select sum size by sym from T where p>151
sym | size
----| ----
AAPL| 400
MSFT| 500

q)/ fby, and membership in a literal or a global list
q)select from T where p>(avg;p) fby sym
sym  p   size
-------------
MSFT 300 200
AAPL 155 250
q)syms:`MSFT`GOOG
q)select from T where sym in syms
sym  p   size
-------------
MSFT 300 200
MSFT 298 300

q)/ the first or last n rows, optionally after an ascending sort
q)select[2;<p] from T
sym  p   size
-------------
AAPL 150 100
AAPL 152 150
q)select[-2] from T
sym  p   size
-------------
MSFT 298 300
AAPL 155 250

q)/ a result is a GPU table: query it again without copying it back
q)select from (select from T where p>151) where sym=`AAPL
sym  p   size
-------------
AAPL 152 150
AAPL 155 250

q)/ exec returns a GPU vector, or a dictionary of them
q)exec p from T
150 300 152 298 155
```

Use the functional form for what qSQL cannot express on a GPU table:

| Not supported on a GPU table | Instead |
|---|---|
| `update ... from T` (signals `type`) | [`.gpu.update`](#update) |
| `delete ... from T` (signals `type`) | [`.gpu.drop`](#drop) for columns, a `select` with the opposite `where` for rows |
| `select[>c]`, a descending sort (signals `nyi`) | [`.gpu.from`](#from) the rows and sort on the CPU |
| a lambda's local or argument in a query, as in `{select from T where p>x}` | build the query with its value: ``{?[T;enlist(>;`p;x);0b;()]}`` |
| `select distinct`, `exec ... by`, `exec k!v` | [`.gpu.from`](#from) the columns and finish on the CPU |

### Functional form

`.gpu.select` mirrors the [functional query operator](https://code.kx.com/q/basics/funsql/) `?[t;c;b;a]`, and `?[T;c;b;a]` on a GPU table calls it.
```syntax
.gpu.select[t;c;b;a]
```

Where:

- `t` is a table with all columns on device.
- `c` is the `where` phrase, a list of constraints.
- `b` is the `by` phrase.
- `a` is the `select` or `exec` phrase.

> **Note — All columns of `t` must be resident on the GPU.**
>
>
### Expression names and membership

Unquoted expression names resolve to a table column first, then to a q variable. A qSQL `select` on a GPU table uses the same interpreter rules. A column therefore takes precedence over a global of the same name; the special row-index name `i` retains its existing meaning. Variables can be globals, current-namespace names, or qualified names such as `.filters.assets`. Unknown names error; existing selector and parameter validation errors are retained. Caller-local lambda arguments are not captured.

Variable values must be supported primitive atoms or flat homogeneous lists. A symbol read from a variable is data, not another name to resolve. Quoted symbols remain literal values. Variables are read again for each query, and projected variable lists own their result storage.

`in` accepts literal sets, atom/list variables, columns, and expressions composed of supported interpreter operations. For example:
```q
q).filters.assets:`a`b
q)T:.gpu.to ([]y:`a`b`c;a:`c`a`b)
q)/ The RHS is the table column, not a global named a.
q).gpu.select[T;enlist(in;`y;`a);0b;()]
q)/ A qualified variable supplies an independent membership set.
q).gpu.select[T;enlist(in;`y;`.filters.assets);0b;()]
q)/ A computed device set is also accepted.
q)N:.gpu.to ([]x:1 2 3;a:0 1 2)
q).gpu.select[N;enlist(in;`x;(+;`a;1));0b;()]
```

An atom is a singleton membership set; an empty set matches nothing. Device-produced sets are searched without modifying their source data. Later `where` clauses see surviving table rows, but external variable lists are not filtered. Grouped membership searches column-derived sets within each group and external sets in full. As in q, aggregates over external vectors instead partition those vectors by the query groups and require a matching row count. Vector arithmetic checks lengths; a singleton vector is not an atom and does not broadcast.

> **Note — Mixed-type membership follows q by the length of the set and where the `in` is**
>
>
> When the set's type differs from the column's, q's rule depends on how many elements the set has and, for an empty set,
> on where the `in` is written. `in` follows it. Anything q accepts that the GPU cannot reproduce exactly is refused with
> `GPU_NOT_IMPLEMENTED` (never answered differently), and what q rejects with `type` is `GPU_INVALID_STATEMENT`.
>
> - **An atom or a one-element set** is compared as `=` across numeric, temporal, character and boolean types
>   (`1 2 3h in enlist 1i`). q rejects some temporal pairs with `type`: month against minute, second, time, timespan or
>   datetime, and date against minute, second, time or timespan, in either order. So does `in`. A long column against an
>   int-backed temporal atom (month, date, minute, second, time), or the reverse, narrows the long to int with saturation
>   first (`0Wd` matches a long of `2147483648` or more); against a plain int it is exact.
> - **Refused:** the pairings q accepts for which the GPU has no comparison (timestamp with month, timestamp with timespan,
>   month with date), date with datetime (the conversions differ from q's at nulls and infinities), and anything where q's
>   float tolerance (`3.0000000000000004 = 3`) could change the answer: a float, real or datetime column against another
>   type, or a float atom other than an integer of magnitude up to 1e9, a null or an infinity.
> - **An empty set of another type** matches nothing only when the `in` is the whole of the first `where` clause and its left
>   side is a bare table column. An expression, `i` or a variable on the left, a later clause, a clause that merely contains
>   it, or a select, exec, by or update expression, gives `type` and so does `in`. The int/long pair is accepted everywhere.
> - **A set of two or more elements** is accepted only for int and long. An int column against a long set compares exactly.
>   A long column against an int set narrows each column value to int first, saturating at the int limits (`2147483648` and
>   `0W` match `0Wi`, `-0W` matches `-0Wi`, a long null matches `0Ni`) and compares as int. Every other pairing is a q `type`
>   error.
> - **Attributed columns (`g#`, `s#`, `u#`, `p#`)**: q searches the column's index for an atom or a one-element set, and for
>   a `p#` column in the first `where` clause for a longer set too, which narrows the set to the column's type. The GPU does
>   not reproduce that search, so these are refused with `GPU_NOT_IMPLEMENTED`: an atom of another type over any attribute;
>   a one-element set of another type over `p#` or `s#`; a set of two or more of another type over `p#` in the first `where`
>   clause; and, over `s#`, a mixed set that a plain column would reject with `type` (q's binary search accepts it). A
>   one-element set over `g#` or `u#`, and a longer set over `g#` or `u#`, is compared as on a plain column. Outside the
>   first `where` clause, `p#` behaves like a plain column.
> - **A set whose length is not known when the query is planned** (a column or a computed set) is accepted for an int
>   column against a long set; any other mismatched type gives `GPU_NOT_IMPLEMENTED`, because the result depends on the length.
>
Host literals and directly resolved host lists use sorted membership-specific copies; computed sets use GPU radix sorting. Eligible first-clause filters on a `g#` column also reuse its index for variable and computed RHS sets. Newly eligible floating-point cases retain the ordinary membership path to preserve NaN equality. These optimizations never reorder the source data.

Existing operation, mixed-type membership, and enum-domain restrictions still apply. This does not enable arbitrary q functions or nested-list variable data.

> **Tip — Run `parse` on a qSQL statement to see how to represent it as a functional select**
>
>
> ```q
> q)parse "select p_min:min p, p_max:max p, p_avg:avg p by sym from t"
> ?
> `t
> ()
> (,`sym)!,`sym
> `p_min`p_max`p_avg!((min;`p);(max;`p);(avg;`p))
> ```
>
```q
q)t:([]sym:`AAPL`MSFT`AAPL`MSFT`AAPL;p:150 300 152 298 155;size:100 200 150 300 250)
q)T:.gpu.to t

q)/ select all columns
q).gpu.select[T;();0b;()]
sym  p   size
-------------
AAPL 150 100
MSFT 300 200
AAPL 152 150
MSFT 298 300
AAPL 155 250

q)/ select specific columns
q).gpu.select[T;();0b;`sym`p!(`sym;`p)]
sym  p
--------
AAPL 150
MSFT 300
AAPL 152
MSFT 298
AAPL 155

q)/ where sym = `AAPL - enlist the symbol to distinguish it from a column name
q).gpu.select[T;enlist(=;`sym;enlist`AAPL);0b;()]
sym  p   size
-------------
AAPL 150 100
AAPL 152 150
AAPL 155 250

q)/ multiple where conditions are ANDed
q).gpu.select[T;((=;`sym;enlist`AAPL);(>;`p;151));0b;()]
sym  p   size
-------------
AAPL 152 150
AAPL 155 250

q)/ aggregation by group
q).gpu.select[T;();enlist[`sym]!enlist`sym;`p_min`p_max`p_avg!((min;`p);(max;`p);(avg;`p))]
sym  p_min p_max p_avg
-------------------------
AAPL 150   155   152.3333
MSFT 298   300   299

q)/ computed column: avg price * size by sym
q).gpu.select[T;();enlist[`sym]!enlist`sym;enlist[`avgpxsz]!enlist(avg;(*;`p;`size))]
sym  avgpxsz
-------------
AAPL 25516.67
MSFT 74700

q)/ unary ops in the select phrase
q).gpu.select[T;();0b;enlist[`logp]!enlist(log;`p)]
logp
--------
5.010635
5.703782
5.023881
5.697093
5.043425
```

### Select or exec

As in native q, the `?` operator runs either a `select` or an `exec`, depending on the arguments provided for `b` and `a`:

**`b`** | **`a`** | **Behavior** | **Result**
---|---|---|---
`0b` | Dictionary or `()` | `select` | Table
Dictionary | Dictionary or `()` | `select` with `by` | Keyed table
`()` or `` `symbol$()`` | Single expression | `exec` | Vector, or an atom if the expression aggregates
`()` or `` `symbol$()`` | Dictionary | `exec` | Dictionary of name → vector/atom

A "single expression" is anything that is not a dictionary. For example, it can be a symbol atom naming a column, or a parse-tree expression such as ``(sum;`p)``.

For ungrouped `exec` queries, vector results remain on the GPU. Scalar results are returned as q atoms on the CPU.
```q
q)t:([]sym:`AAPL`MSFT`AAPL`MSFT`AAPL;p:150 300 152 298 155;size:100 200 150 300 250)
q)T:.gpu.to t

q)/ return one column
q).gpu.select[T;();();`p]
150 300 152 298 155

q)/ return a scalar aggregate
q).gpu.select[T;();();(sum;`size)]
1000

q)/ a dictionary by with a dictionary phrase is a select, so this returns the same
q)/ keyed table as `select minp:min p, maxp:max p by sym from t`
q).gpu.select[T; (); (enlist `sym)!enlist `sym; `minp`maxp!((min;`p);(max;`p))]
sym | minp maxp
----| ---------
AAPL| 150  155
MSFT| 298  300
```

> **Warning — Current limitations**
>
>
> - Grouped `exec` is not supported. This includes a dictionary for `b` with a single expression for `a`.
>
> - A symbol atom for `b` (``?[T;();`a;…]``) is not supported, on keyed and plain tables alike. Use a dictionary: ``enlist[`a]!enlist`a``.
>
> - Using `1b` for `b` (`select distinct`) is not supported.
> - An empty `exec` phrase (`()`) with `()` for `b` (q's last-row forms) is not supported.
> - An empty `by` dictionary (`()!()`) is supported only with a dictionary phrase, where, as in q, it makes one group of every surviving row: a non-aggregate column is returned as a single nested cell, and a table or where clause with no surviving rows gives an empty result. The `exec` and empty-phrase forms are not supported.
>
> - Non-empty symbol-vector `by` forms, such as ``enlist `sym`` or `` `sym`date``, are not supported. Use a dictionary for named or multiple grouping columns.
>
### Where clause

- `c` is a list of parse-tree expressions, each evaluating to a boolean vector.
- Expressions can use all supported operations.
- `()` (empty list) means no constraints.
- Multiple conditions are applied sequentially.

> **Tip — ANDing clauses together may lead to better performance when individual clauses are not highly selective.**
>
>
> **Note — For enumerated columns, host symbol membership sets are resolved against the column's domain. Already-enumerated sets must use the same domain; cross-domain membership is not supported.**
>
>
> **Note — When comparing to symbol atoms, `enlist` distinguishes a value from a column name. For example, use ``(=;`sym;enlist`assetA)``.**
>
>
### By clause

- `b` is a dictionary mapping output key names to input column names: `` enlist[`sym]!enlist`sym ``.
- `0b` or `()` means no grouping. Use `0b` for `select` semantics and `()` for `exec` semantics.

> **Note — The output is usually sorted by the key columns; this is not true for symbol key columns. Try using an enumeration instead.**
>
>
### Select clause

- `a` is a dictionary mapping output column names to input columns or parse-tree expressions.
- `()` selects all columns.
- Bare symbols in dictionary values resolve to columns first, then q variables. To reference a literal symbol, `enlist` it.

### fby

[`fby`](https://code.kx.com/q/ref/fby/) aggregates one column over the groups of another and gives every
row its own group's result, so the output is as long as the table.

The aggregate and the column it applies to are paired with `enlist`, mirroring qSQL's `(agg;col)`:
```q
q)/ these are equivalent
q)select from t where y > (avg;y) fby x
q)?[t;enlist(>;`y;(fby;(enlist;avg;`y);`x));0b;()!()]
```

A multi-column group key is a table, so it takes the usual flipped-dictionary form —
``(+:;(!;enlist`x`z;(enlist;`x;`z)))`` is `([]x;z)`.
```q
q)t:([]sym:`AAPL`MSFT`AAPL`MSFT`AAPL;p:150 300 152 298 155;size:100 200 150 300 250)
q)T:.gpu.to t

q)/ each row gets its own sym's average price
q).gpu.select[T;();0b;enlist[`p_avg]!enlist(fby;(enlist;avg;`p);`sym)]
p_avg
--------
152.3333
299
152.3333
299
152.3333

q)/ grouped by two keys, the second computed: ([]sym;size>150)
q).gpu.select[T;();0b;enlist[`p_max]!enlist(fby;(enlist;max;`p);(+:;(!;enlist`sym`big;(enlist;`sym;(>;`size;150)))))]
p_max
-----
152
300
152
300
155

q)/ the canonical use - filter rows against their own group's aggregate
q).gpu.select[T;enlist(>;`p;(fby;(enlist;avg;`p);`sym));0b;()]
sym  p   size
-------------
MSFT 300 200
AAPL 155 250
```

> **Tip — Let q build the phrase**
>
>
> `fby` trees are wordy, so it is often easier to `parse` a qSQL statement and take the phrase you
> want — the select phrase is the last element, and the `where` phrase is element 2 (**doubly**
> nested, so it needs a `first`):
>
> ```q
> q)last parse "select p_avg:(avg;p) fby sym from t"          / select phrase
> q)first (parse "select from t where p > (avg;p) fby sym") 2  / where phrase
> ```
>
Supported aggregates are `sum`, `min`, `max`, `count`, `first`, `last`, `avg`, `var` and `dev`.
Also supported operations are `ratios`, `deltas`, `mins`, `maxs`, `sums`, `prds`, `fills`, `prev`, `next`, `iasc` and `rank`.
The group key may be a column, `` `i ``, an expression, or a table of several columns. Under a `by` clause the fby is evaluated per query group, as in q.

### Supported operations

Not all types are supported for all operations, but in general the supported types are scalars and 1D vectors of basic atomic types.

- Binary ops: `=`, `<>`, `<`, `>`, `<=`, `>=`, `+`, `-`, `*`, `%`, `|`, `&`, `in`, `within`, `xbar`, `xexp`, `^`
- Unary ops: `abs`, `log`, `exp`, `sin`, `asin`, `cos`, `acos`, `tan`, `atan`, `floor`, `ceiling`, `sqrt`
- Aggregate ops: `sum`, `min`, `max`, `avg`, `count`, `first`, `last`, `var`, `dev`, `wavg`
- Scan ops: `sums`, `prds`, `mins`, `maxs`, `fills`, `ema`
- Each-prior ops: `ratios`, `deltas`
- Windowed scan ops: `mavg`, `msum`, `mmin`, `mmax`, `mdev`
- Shift ops: `prev`, `next`, `xprev`
- Sorting ops: `iasc`, `rank`, `xrank`
- Grouped broadcast: `fby` (refer to [fby](#fby))
- Casting

> **Note — In parse-tree form, `<>` is `(';~:;=)`, `>=` is `(';~:;<)`, and `<=` is `(';~:;>)`.**
>
>
> **Info — Results may deviate slightly from base kdb+. For more information, refer to [CUDA Mathematical Functions](https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/mathematical-functions.html#cuda-c-mathematical-standard-library-functions).**
>
>
> **Note — Enum domains in binary operations**
>
> Enum values compare by their domain index, so an equality (`=`) or fill (`^`) between enum values of different domains is rejected with `GPU_NOT_IMPLEMENTED`, including when one side is a scalar. q compares them by symbol. Use `aj`, `aj0`, `ajf`, `ajf0` or `bin` to match enum keys by symbol across domains.
>
> **Warning — Seeded scan over an empty input**
>
> For a seeded scan (for example, `fills` with a seed value) over an empty column, when the seed type differs from the input type, `.gpu.select` returns a typed empty list instead of q's typeless `()`.
>
> **Note — Seeded `fills`**
>
> The seed of `fills[seed;x]` must be an atom, as in q: a literal, a constant expression (`1+2`), an aggregate (`max x`), or a scan of one (`sums max x`). Any other seed (a column, a vector, a dictionary, a table) is a q `nyi` error and gives `GPU_INVALID_STATEMENT`, in a select, update, where or by clause. Under `by`, an aggregate seed seeds each group with its own value. Not implemented: an aggregate seed over a constant input under `by` (`fills[max x;5] by g`) gives `GPU_NOT_IMPLEMENTED`. Rejected with `GPU_INVALID_STATEMENT` although q accepts them: a seeded `fills` as the seed under `by` (`fills[fills[max x;max y];y] by g`) and an `mmax` or `mmin` aggregate seed (`fills[(mmax;2;(max;`x));y]`).
>
> **Warning — Month cast range**
>
> Casting a month to a date or timestamp matches kdb+ for months from `-23999` (`0000.02m`) up to about `70555437`. Outside that range, `.gpu.select` returns the calendar-correct day count, or an infinity whose sign follows the month, and may differ from kdb+.
>
## setMemRelThres

_Set the memory release threshold_
```syntax
.gpu.setMemRelThres num_bytes
```

> **Warning — Deprecated: use [`.gpu.config`](#config) instead.**
>
>
> ``.gpu.setMemRelThres num_bytes`` is equivalent to ``.gpu.config ([memRelThres:num_bytes])``.
>
Set the memory release threshold for the GPU memory pool of the active device to `num_bytes` bytes.
Refer to the [`memRelThres` setting](#settings) of [`.gpu.config`](#config) for what the threshold does.
```q
q)/ set the current active device to device 1
q).gpu.setDev 1
q)/ set memory release threshold of device 1 to 10GB
q).gpu.setMemRelThres 10*1024*1024*1024
```

## sublist

_Head, tail or slice of a list_
```syntax
.gpu.sublist[x;y]
```

Where `x` is:

- a positive long atom, returns the first `x` items of `y`.
- a negative long atom, returns the last `x` items of `y`.
- a long vector, returns up to `x[1]` items from `y`, starting at item `x[0]`. The result contains no more items than are available in `y`.

`y` can also be a tensor, sliced on its leading axis: `.gpu.sublist[2;X]` and `2 sublist X` are the first two rows of the matrix `X`, as `2 sublist x` is of the host matrix `x`. A count that reaches the length returns `y` itself, as q does.

> **Warning — Negative starting points are not yet implemented for GPU-resident vectors or tensors.**
>
>
```q
q)/ Head or tail, similar to .gpu.take but limited to the list length
q)A:.gpu.to a:2 3 5 7 11
q).gpu.sublist[3;A]
2 3 5
q).gpu.sublist[3;A]
2 3 5
q).gpu.sublist[10;A]
2 3 5 7 11
q).gpu.take[10;A]
2 3 5 7 11 2 3 5 7 11

q)syms:-10?`3
q)T:.gpu.to t:`time xasc([]time:2026.07.10D12:32+5?00:00:00;sym:5?syms;px:95+5?10)
q)t
time                          sym px 
-------------------------------------
2026.07.10D18:48:49.000000000 adl 104
2026.07.11D03:15:53.000000000 cgl 97 
2026.07.11D04:57:05.000000000 hmc 104
2026.07.11D06:32:25.000000000 fjk 96 
2026.07.11D09:26:01.000000000 cgl 104
q).gpu.sublist[-2] T
time                          sym px 
-------------------------------------
2026.07.11D06:32:25.000000000 fjk 96 
2026.07.11D09:26:01.000000000 cgl 104


q)/ Slice
q)A:.gpu.to a:2 3 5 7 11
q).gpu.sublist[1 2;A]
3 5

q) / Negative start for GPU nyi
q)a
2 3 5 7 11
q)sublist[-2 5;a]
0N 0N 2 3 5
q).gpu.sublist[-2 5;a]
0N 0N 2 3 5
q).gpu.sublist[-2 5;A]
'nyi
```

## sync

_Wait for outstanding GPU work_
```syntax
.gpu.sync[]
```

Blocks the calling CPU thread until all work on the currently selected GPU has completed. Returns null.

> **Tip — `.gpu.sync` is useful to avoid `'wsfull` when the CPU provides work faster than the GPU can consume it, or when more explicit control of memory is needed.**
>
>
```q
q)a:til 1000000
q)-16!a
1i                  / only 1 reference to a
q)A:.gpu.to a       / returns before the copy to device is completed
q)-16!a             / the gpu stream owns a reference to a to facilitate this
2i
q)r:.gpu.from A     / implicit sync before return to q, but .gpu.from does no destructor work.
q)-16!a             / gpu stream still owns a reference to a, despite the copy having completed
2i

q).gpu.sync[]       / explicit sync, waits for the stream to complete then completes any destructor work
q)-16!a
1i

q)/ Most other apis will run any outstanding destructor work but without syncing.
q)a:til 1000000; b:til 1000000  / Get new objects, both rc=1
q)A:.gpu.to a; B:.gpu.to b      / Both return A & B before either copy is complete:
q)-16!a
2i
q)-16!b
2i
q)B:.gpu.to b                   / Both copies have completed, doing another doesn't sync but does clean up the refs.
q)-16!a
1i
q)-16!b
2i
q).gpu.sync[]                   / And a final sync cleans up the latest ref.
q)-16!a
1i
q)-16!b
1i

q)a:til 1000000; b:til 1000000  / Get new objects, both rc=1
q)A:.gpu.to a; .gpu.sync[]; B:.gpu.to b / Ensures A is done first, second copy can clean up its ref.
q)-16!a
1i
q)-16!b
2i
```

## take

_Select leading or trailing items from a list or dictionary, named entries from a dictionary, or named columns from a table_
```syntax
.gpu.take[x;y]
```

Where `x` is:

- a positive long atom, returns the first `x` items of `y`
- a negative long atom, returns the last `x` items of `y`
- a symbol list, returns the specified keys/column of a dictionary/table `y`
```q
q)A:.gpu.to a:til 5
q)D:.gpu.to d:`a`b!(a;a+10)
q)T:.gpu.to t:flip d

q).gpu.take[2;A]
0 1
q).gpu.take[2;D]
a| 0  1  2  3  4
b| 10 11 12 13 14
q).gpu.take[`b`a;D]
b| 10 11 12 13 14
a| 0  1  2  3  4 
q).gpu.take[2;T]
a b
----
0 10
1 11
q).gpu.take[-2;T]
a b
----
3 13
4 14
q).gpu.take[`b;T]
b
--
10
11
12
13
14
```

## tensor

_Create dense GPU tensors and perform arithmetic, reductions, cumulative scans, sorting, concatenation and linear algebra on the GPU_

Tensor objects are distinct from GPU table columns, and are returned as virtual tensors. Construction and transfer accept homogeneous q int, long, real, and float data (`KI`, `KJ`, `KE`, and `KF`), stored densely in row-major order. Elementwise unary and binary arithmetic, reductions and scans support all four dtypes with the operation-specific promotion described below. `mmu`, `inv`, `chol`, and `lsq` also take all four dtypes, as does PCA; `mmu` and `lsq` accept mixed pairs. Concatenation, sorting and `gather` take all four dtypes, and typed storage, transfers and random generation retain their existing dtype support.
```syntax
.gpu.tensor.to x
.gpu.tensor.from tensor
.gpu.tensor.shape tensor
.gpu.tensor.meta tensor
.gpu.tensor.reshape[shape;tensor]
.gpu.tensor.transpose tensor
.gpu.tensor.cast[dtype;tensor]

.gpu.tensor.mmu[left;right]
.gpu.tensor.inv tensor
.gpu.tensor.chol tensor
.gpu.tensor.lsq[targets;predictors]
.gpu.tensor.sum[axes;keepdims;tensor]
.gpu.tensor.avg[axes;keepdims;tensor]
.gpu.tensor.prd[axes;keepdims;tensor]
.gpu.tensor.min[axes;keepdims;tensor]
.gpu.tensor.max[axes;keepdims;tensor]
.gpu.tensor.first[axes;keepdims;tensor]
.gpu.tensor.last[axes;keepdims;tensor]
.gpu.tensor.sums[axis;tensor]
.gpu.tensor.prds[axis;tensor]
.gpu.tensor.mins[axis;tensor]
.gpu.tensor.maxs[axis;tensor]
.gpu.tensor.asc[axis;tensor]
.gpu.tensor.desc[axis;tensor]
.gpu.tensor.pca[configuration;tensor]
.gpu.tensor.concat[axis;tensors]
.gpu.tensor.take[count;tensor]
.gpu.tensor.view[count;tensor]
.gpu.tensor.gather[spec;tensor]

.gpu.tensor.random.create configuration
.gpu.tensor.random.meta generator
.gpu.tensor.random.reset generator
.gpu.tensor.random.bits[generator;shape]
.gpu.tensor.random.uniform[generator;shape;dtype]
.gpu.tensor.random.normal[generator;shape;mean;stddev;dtype]
.gpu.tensor.random.lognormal[generator;shape;mean;stddev;dtype]
.gpu.tensor.random.poisson[generator;shape;lambda]
.gpu.tensor.random.randint[generator;shape;low;high]
```

`shape` and `strides` in `.gpu.tensor.meta` are long vectors. `dtype` is the q short type code, `device` is the allocation device, and `contiguous` reports logical row-major contiguity. The `axisNames` value is a general list containing one symbol vector per axis; `` `symbol$() `` denotes an unnamed axis.
```q
q)x:.gpu.tensor.to (1 2 3f;4 5 6f)
q).gpu.tensor.shape x
2 3
q).gpu.tensor.transpose x
1 4
2 5
3 6
q).gpu.tensor.reshape[enlist 6;.gpu.tensor.transpose x]
1 4 2 5 3 6f
q).gpu.tensor.meta x
dtype     | 9h
rank      | 2
shape     | 2 3
strides   | 3 1
device    | 0
contiguous| 1b
axisNames | (`symbol$();`symbol$())
```

`reshape` requires the same element count and otherwise signals `length`. A row-contiguous tensor (`contiguous` is `1b`, as it is for a transpose view with an extent of 1) is reshaped by metadata alone and shares its storage. A transpose view whose extents both exceed 1 (`contiguous` is `0b`) is first copied into a new row-contiguous tensor, so the result does not share storage with the view, and its elements follow the view's logical order. That copy, like the one `from` makes of such a view, needs free device memory of about the size of the view while the call runs. `reshape` may produce tensors above rank 2 (up to rank 8), consumed by unary/binary arithmetic, reductions, scans, sorting and concatenation. Host transfer remains rank 0–2: reshape a contiguous higher-rank result to a vector before downloading it. `transpose` is a metadata-only rank-2 view. `mmu` supports vector-vector, vector-matrix, matrix-vector, and matrix-matrix forms, and accepts transpose views. Its operands can have any combination of `KI`, `KJ`, `KE`, and `KF` dtypes. The result is `KF` if either operand is `KF`, otherwise `KE` if either is `KE`, otherwise `KF` for integer-only pairs. Operands are converted on the GPU before multiplication; integer nulls become floating nulls and propagate through the product, even when multiplied by zero. Other integer values are cast numerically. Conversion can round integers above `2^24` in `KE` or `2^53` in `KF`; integer products do not use wrapping arithmetic. Each converted operand needs temporary device memory equal to its element count times the result element width. Matching `KE` and matching `KF` pairs need no conversion. A float64 matrix times its own transpose (`mmu[x;transpose x]` or `mmu[transpose x;x]`) is a symmetric product; builds for Ada GPUs (sm_89) compute a large one faster, from 1392 rows (or columns) of the product in multiples of 16, with a contraction length of 100 to 10000 in multiples of 4 (measured on an L4). The values are the same product to rounding, though for some data (constant rows) the rounding error can be several times the general product's. Other sizes, other architectures and products requiring dtype conversion use the general product, as do `KE` tensors. `inv` and `chol` preserve `KE`/`KF` and promote `KI`/`KJ` to `KF`. `lsq` uses the same promotion as `mmu`: `KF` takes precedence over `KE`, and integer-only pairs return `KF`. Integer nulls are converted to floating nulls before solving; other integers are cast numerically, with the same precision limits as `mmu`. PCA also preserves `KE`/`KF` and promotes integers to `KF`. `inv` accepts square matrices. `chol` returns the lower-triangular Cholesky factor `L` of a symmetric positive definite matrix, with `A ~ mmu[L;transpose L]` and exact zeros above the diagonal; it reads only the lower triangle of its input and signals `GPU_TENSOR_NOT_POSITIVE_DEFINITE` when there is no factor. `lsq` uses q's features-by-observations contract:
```text
targets[m,n] lsq predictors[k,n] -> coefficients[m,k]
```

These solvers compute in the result dtype: `KE` uses float32 factorization and solves rather than computing in float64 and narrowing. Float32 has lower accuracy, particularly for `lsq`, whose normal equations square the predictor matrix's condition number; use `KF` for ill-conditioned problems or tight residual bounds. `inv` and `chol` convert directly into their working copy; `lsq` needs an additional device buffer of `elements * result_element_width` bytes per converted operand. Singular `inv` and rank-deficient or underdetermined `lsq` return correctly shaped floating nulls in the result dtype. Cholesky failure remains `GPU_TENSOR_NOT_POSITIVE_DEFINITE`.

`mmu`, `inv`, `chol`, `lsq` and `pca` accept transpose views. `inv` and `chol` give exactly the result of the row-contiguous tensor with the same elements; `mmu` and `lsq` agree with it to rounding (a numerically borderline rank-deficient `lsq` problem may differ in whether it returns nulls). Unsupported ranks and mismatched dimensions are rejected with `GPU_TENSOR_SHAPE`.

Arithmetic, reductions, scans, `asc`, `desc`, `concat`, `mmu`, `inv` and `lsq` enqueue work on the device stream. Singular and rank-deficient `inv`/`lsq` results are resolved on the device. Downloads wait for results; the table bridge remains device-to-device. `chol` and PCA are explicit exceptions: they synchronize to validate numerical status, while keeping their outputs resident.

### Casting tensors

`.gpu.tensor.cast[dtype;tensor]` converts between int, long, real, and float on the device:

| Target symbol | Short type code | Character code |
|---|---|---|
| `` `int `` | `6h` | `"i"` |
| `` `long `` | `7h` | `"j"` |
| `` `real `` | `8h` | `"e"` |
| `` `float `` | `9h` | `"f"` |
```q
x:.gpu.tensor.to 1.2 -2.5 0n;
i:.gpu.tensor.cast[`int;x]; / q rounding, including -2.5 to -3
f:.gpu.tensor.cast[9h;i];
e:.gpu.tensor.cast["e";f];
```

Ranks 0–8, empty tensors, transpose views, and offset views are supported. Shape and all axis names are preserved. If the target dtype already matches, the result is a retained reference to the original tensor: no allocation or kernel, with the same strides, offset, and value bits. Otherwise the result owns independent row-contiguous storage in logical element order; the source is unchanged. Changing dtype needs device memory of `elements * target_element_width`, with no additional promoted input buffer. The conversion enqueues on the current device stream without a host-result synchronization.

Value conversions match native q's numeric casts, not C++ integer truncation:

- Integer nulls and floating NaNs become destination nulls. A dtype-changing cast does not preserve NaN payloads; an identity cast preserves every bit, including payloads and signed zero.
- Floating-to-integer conversion rounds halves away from zero and saturates out-of-range values and floating infinities to destination integer ±infinity. It follows q's binary64 add-half behavior even near `2^52`, where casting the integral float `4503599627370497f` to long yields `4503599627370498j`.
- Integer widening is exact; narrowing saturates to destination integer ±infinity rather than wrapping. Non-null integer infinity sentinels are ordinary numeric values when widened or converted to floating types.
- Floating conversion uses nearest-even rounding and preserves signed zero. As in q, widening a real subnormal to float yields signed zero; narrowing a float to a real subnormal also yields signed zero after rounding. Floating overflow produces signed infinity. Integer-to-floating casts can lose precision above `2^24` for real or `2^53` for float. As in q, long-to-real conversion rounds through float64; at large halfway values its result can differ from a direct integer-to-f32 conversion.

The target selector is validated before the tensor, including identity casts. Unknown symbols, character codes, and unsupported short codes report `domain`. Wrong selector types report `type`: int/long type-code atoms, vectors, and `::` are not accepted. Non-tensor inputs report `GPU_TYPE_UNSUPPORTED`; tensors on another device report `GPU_WRONG_DEVICE`. These explicit cast rules do not change arithmetic or solver promotion rules.

### Tensor arithmetic and broadcasting

Unary operations are direct members of `.gpu.tensor`:
```text
neg abs exp log sqrt recip floor ceil erf erfc ncdf
sin cos tan asin acos atan sinh cosh tanh sign square rsqrt expm1 log1p
exp2 log2 log10 trunc round
```

`ceiling` aliases `ceil`; `signum` aliases `sign`. Each accepts a `KE`, `KF`, `KI` or `KJ` tensor and returns an independent row-contiguous tensor of the same shape and axis names. Real and float inputs preserve their dtype. Integer inputs to `neg`, `abs`, `floor`, `ceil`, `sign`, `square`, `trunc` and `round` preserve `KI` or `KJ`; integer nulls are preserved. The other unary operations convert integer inputs to `KF`, mapping integer nulls to the float null. Ranks 0–8, empty tensors and rank-2 transpose views are supported. `round` uses ties-to-even. For floating inputs, `floor`, `ceil`, `trunc` and `round` preserve the input dtype. `sign` propagates NaN and maps either signed zero to positive zero. `ncdf` is the standard normal cumulative distribution.

The binary operations are `add`, `sub`, `mul`, `div`, `pow`, `minimum`, `maximum`:
```q
x:.gpu.tensor.to (1 2 3f;4 5 6f);
row:.gpu.tensor.to 10 20 30f;
.gpu.tensor.add[x;row] / 2 by 3: adds the row to every row
.gpu.tensor.div[1f;x] / scalar on either side
.gpu.tensor.exp x
```

Operands may be `KE`, `KF`, `KI` or `KJ` tensors, or scalar atoms of those types; at least one operand must be a tensor. Boolean, temporal, symbol, vector and other unsupported operands are rejected. Floating operands dominate integer ones: the result is `KF` if either operand is `KF`, otherwise `KE` if either operand is `KE`. With only integer operands, `add`, `sub`, `mul`, `minimum` and `maximum` preserve integers, promoting `KI`+`KJ` to `KJ`; integer-only `div` and `pow` return `KF`. Integer nulls propagate, including to the appropriate floating null when promoted. Integer addition, subtraction and multiplication wrap at the result width. There is no generic `map`/`ew` selector and no duplicate top-level `.gpu` arithmetic API.

Shapes align from the trailing axis, following NumPy/PyTorch broadcasting: dimensions must be equal or one must be 1, and missing leading dimensions act as
1. `[m,n]` with `[n]` yields `[m,n]`; `[m,1]` with `[1,n]` yields `[m,n]`.
An extent of 0 with 1 yields 0. Broadcast axes use stride-zero reads; expanded operands are not copied. Results own independent storage and inputs are unchanged. Both tensor operands must belong to the current GPU. A tensor operand may be row-contiguous or a rank-2 transpose view, and the result is a new row-contiguous tensor.

Names propagate from non-expanded axes. Two named contributors to the same result axis must match in value and order or raise `GPU_AXIS_MISMATCH`; no implicit reordering occurs. An expanded singleton's name is discarded. Scalar atoms supply no names.

Floating NaNs, infinities and signed zeros are valid arithmetic inputs. Both `minimum` and `maximum` propagate NaN; table-select maximum has different q-specific null semantics. Tensor power follows floating mathematical power, not the table operator's q-specific special cases. Domain failures produce floating NaNs or infinities as appropriate. No NaN-payload identity or bitwise agreement between NumPy, PyTorch and CUDA is promised, including tied signed-zero min/max results.

### Axis-aware tensor reductions
```q
x:.gpu.tensor.to (1 2 3f;4 5 6f);
.gpu.tensor.sum[0;0b;x]  / 5 7 9f
.gpu.tensor.avg[0;0b;x]  / 2.5 3.5 4.5
.gpu.tensor.prd[-1;0b;x] / 6 120f
.gpu.tensor.min[-1;1b;x] / (enlist 1f;enlist 4f)
.gpu.tensor.max[::;0b;x] / 6f, a rank-zero tensor
.gpu.tensor.first[0;0b;x] / 1 2 3f
.gpu.tensor.last[-1;0b;x] / 3 6f
.gpu.tensor.last[::;0b;x] / 6f, a rank-zero tensor
.gpu.tensor.last[0;0b;.gpu.tensor.to (1 2 3;4 5 6)] / 4 5 6, a long vector
```

`sum`, `avg` (the arithmetic mean), `prd` (product), `min` and `max` reduce the given axes of a `KE`, `KF`, `KI` or `KJ` tensor, and `first` and `last` select an element of them, as described below. All preserve the input dtype except integer `avg`, which converts inputs to `KF` before summing and returns `KF`. Real and float sums, products and means accumulate at their input width. `axes` is `::` (every axis), a long atom, or a long vector. Negative indices count from the last axis. Out-of-range indices, duplicate normalized axes, invalid types and a non-boolean `keepdims` are errors. Multiple axes may be supplied in any order. An empty long vector returns an independent copy, preserving signed-zero bits; integer `avg` still converts to `KF`, including integer nulls to floating nulls. With `keepdims:1b`, reduced axes have extent 1 and no coordinate names; otherwise they disappear. Unreduced names survive. The input may be a row-contiguous tensor or a rank-2 transpose view, and the result is a new row-contiguous tensor that owns its storage. A view gives the same `min`, `max`, `first` and `last` as the row-contiguous tensor with the same elements, and `sum`, `avg` and `prd` that agree to rounding.

Rank-zero tensors accept `::`, atom `0` or atom `-1`, and an empty long vector; nonempty axis vectors are rejected. Rank-zero output stays rank zero with either keepdims setting. An empty surviving dimension produces an empty result.

For `sum`, `avg`, `prd`, `min` and `max`, reducing nothing (a reduced axis of extent zero) gives the operation's identity: `0` for `sum`, `1` for `prd`, positive infinity for `min` and negative infinity for `max`, as in q, and NaN for `avg` (0 divided by 0). Integer extrema use the integer infinities (`0Wi`/`-0Wi` for `KI`, `0Wj`/`-0Wj` for `KJ`). Floating sums and means start from positive zero, so a sum or mean of negative zeros is positive zero; `prd`, `min` and `max` of a lone `-0f` stay `-0f`. `avg` is the sum divided by the number of reduced elements, so it overflows to infinity where the sum does. Integer sums and products wrap modulo the result width; integer means sum in `KF` instead. An integer null poisons its whole reduction, including a floating null result for integer `avg`. Null tracking is separate from wrapping arithmetic: an intermediate value with the null's bits does not poison a reduction unless a null was present in its input.

In `sum`, `avg`, `prd`, `min` and `max`, NaNs propagate, including q floating nulls; infinities follow ordinary arithmetic. This differs from q, whose vector versions of these skip nulls. `min` and `max` return an element of the line, or NaN if any element is NaN, and a tie between `-0f` and `0f` resolves the same way wherever the elements sit: `min` returns `-0f`, `max` returns `0f`, unlike `mins` and `maxs`, which keep the earlier element. `min` and `max` return the same element whatever the order of the reduction, whereas finite floating `sum`, `avg` and `prd` results may differ in low bits from a sequential q or NumPy result because parallel reduction changes the order of the operations, and a `prd` whose partial products overflow or underflow can differ from a sequential product for the same reason. The output remains on the GPU without a host-result synchronization.

`first` and `last` select an element; they do not combine. `first` returns the element at index 0 of every reduced axis and `last` the element at the last index of every reduced axis. Over every axis they give the first and last elements of the tensor in row-major order, and listing the axes in any order gives the same element. The element is copied unchanged: nulls and NaN payloads are not made canonical or propagated, `-0f` stays `-0f` and infinities are unchanged. This matches q, whose `first` and `last` do not skip nulls. They take `KE`, `KF`, `KI` and `KJ` tensors, and the result has the dtype of the input. A reduced axis of extent zero has nothing to select and gives the dtype's null: `0Ne`, `0n`, `0Ni` or `0Nj`.

### Axis-aware tensor cumulative scans
```q
x:.gpu.tensor.to (1 3 2f;4 0 9f);
.gpu.tensor.sums[0;x]  / (1 3 2f;5 3 11f)
.gpu.tensor.sums[-1;x] / (1 4 6f;4 4 13f)
.gpu.tensor.prds[-1;x] / (1 3 6f;4 0 0f)
.gpu.tensor.mins[-1;x] / (1 1 1f;4 0 0f)
.gpu.tensor.maxs[0;x]  / (1 3 2f;4 3 9f)
```

`sums`, `prds`, `mins` and `maxs` return every inclusive running sum, product, minimum or maximum along one axis, preserving the input shape and all axis names. For example, scanning axis `0` of daily log returns shaped `[days,simulations,assets]` with `sums` retains each asset's cumulative log return on every day, and `maxs[0;...]` of that result is each asset's running peak cumulative log return. Use a reduction such as `sum` or `max` when only the total or the peak is required.

`axis` must be one q long atom. Negative indices count from the last axis. Vectors, `::`, functions, other types and out-of-range indices are errors; there is no flattening sentinel or multiple-axis form. Rank-zero tensors accept only axis `0` or `-1` and return an independent rank-zero copy.

Inputs may be `KE`, `KF`, `KI` or `KJ` tensors on the current GPU; all four scans preserve the input dtype. Ranks 0–8, row-contiguous tensors and rank-2 transpose views are supported. Results own independent row-contiguous storage; inputs are unchanged. Empty inputs return an empty result without a scan, and singleton scan axes return independent copies, preserving signed-zero bits.

`sums` and `prds` use floating addition and multiplication at the input width, or integer arithmetic that wraps modulo the input width. Integer nulls poison the remaining prefixes of their own line in all four scans. A wrapped intermediate value with the null's bits does not poison later prefixes unless a null was present in the input. `mins` and `maxs` return an element of the line, and when two elements compare equal the earlier one is kept, so `-0f` and `0f` ties resolve to whichever comes first (unlike the elementwise `minimum` and `maximum`, which promise no tie order). A NaN present in the input, including a q floating null, propagates through the remaining results of its own scan line in all four scans without affecting other lines, as `minimum` and `maximum` propagate NaN. Infinities follow ordinary arithmetic. This differs from native q, which treats a null as the operator's identity in `sums`, `prds`, `mins` and `maxs` (for example `mins 0n 1 2f` is `0w 1 1`).

Finite floating `sums` and `prds` results may differ in low bits from sequential q or NumPy results, and from one run to the next, because parallel scans can change the order of the operations; bitwise agreement is not guaranteed. The same reordering means that a `sums` or `prds` whose partial results overflow, underflow, or produce a NaN from `inf-inf` or `inf*0` can differ from a sequential scan, even to a finite value where the sequential result is infinite. `mins` and `maxs` give the same result on every run.

The operation enqueues work on the device stream without a host-result synchronization. Intermediate values and the result remain on the GPU; download explicitly with `.gpu.tensor.from` when needed.

### Sorting tensors
```q
x:.gpu.tensor.to (3 1 2f;0 9 5f);
.gpu.tensor.asc[-1;x]  / (1 2 3f;0 5 9f)
.gpu.tensor.desc[-1;x] / (3 2 1f;9 5 0f)
.gpu.tensor.asc[0;x]   / (0 1 2f;3 9 5f)
.gpu.tensor.desc[0;x]  / (3 9 5f;0 1 2f)
.gpu.tensor.asc[0;.gpu.tensor.transpose x] / (1 0f;2 5f;3 9f)
w:3 0n 1 0n 2f;
.gpu.tensor.asc[0;.gpu.tensor.to w]  / 0n 0n 1 2 3f
.gpu.tensor.desc[0;.gpu.tensor.to w] / 3 2 1 0n 0n
(.gpu.tensor.from .gpu.tensor.asc[0;.gpu.tensor.to w])~asc w / 1b
```

`asc` and `desc` sort a tensor along one axis, ascending and descending, preserving the shape: axis `-1` of a matrix sorts each row on its own and axis `0` sorts each column. Every line along the axis is sorted independently of the others. This differs from q's `asc` of a list of rows, which orders the rows themselves, and from [`.gpu.asc`](#asc), which sorts a GPU column.

`axis` must be one q long atom, as for the scans. Negative indices count from the last axis. Vectors, `::`, functions and other types signal `GPU_TYPE_UNSUPPORTED`, and an index outside the tensor's rank signals `GPU_TENSOR_SHAPE`; there is no flattening sentinel or multiple-axis form. Rank-zero tensors accept only axis `0` or `-1`.

Sorting accepts `KE`, `KF`, `KI` or `KJ` tensors, and the result has the same dtype and shape. Ranks 0–8, row-contiguous tensors and rank-2 transpose views are supported, and sorting a view gives the same result as sorting a row-contiguous tensor with the same elements. The result is a new row-contiguous tensor that owns its storage; the input is unchanged. An empty tensor gives an empty result, and a rank-zero tensor or an axis of extent 1 gives a copy whose NaNs are made canonical, as in any result. An argument that is not a tensor signals `GPU_TYPE_UNSUPPORTED`, and a tensor on another GPU signals `GPU_WRONG_DEVICE`.

The sort is stable in both directions: elements that compare equal keep their order along the axis. `desc` is therefore not `asc` reversed when there are ties.

Floating-point values order as in q: `-0w` is below every finite value and `0w` above. Every NaN, including the q nulls `0n` and `0Ne`, compares equal to every other NaN, and sorts first in `asc` and last in `desc`. Each NaN in the result is the same quiet NaN, the one q uses for its null, so the sign and payload bits of an input NaN are not preserved. `-0f` and `0f` compare equal and keep their input order in both directions, so `asc` and `desc` of `0f,-0f` both return `0f,-0f`, signs as they came. For a float vector `w`, including one with nulls, `.gpu.tensor.from .gpu.tensor.asc[0;.gpu.tensor.to w]` matches `asc w`, except that q's `asc` sets the `` `s `` attribute and the result here does not. Integers sort in signed order; the q nulls `0Ni` and `0Nj` are the smallest values, so they come first in `asc` and last in `desc`, as in q.

Every axis keeps its names except the sorted one, whose names are cleared because its elements have moved (also when its extent is 1). The input's names are unchanged.

While the call runs it needs free device memory of up to about twice the tensor's size, for the result and a temporary copy that is released afterwards, plus working storage for the sort. A rank-zero tensor and an axis of extent 1 need no temporary copy, and a sort of more than one line, each of a few thousand elements or fewer, needs no device memory beyond the result. The operation enqueues work on the device stream without a host-result synchronization, so results chain into further tensor operations; download explicitly with `.gpu.tensor.from` when needed.

### Concatenating tensors
```q
a:.gpu.tensor.to (1 2 3f;4 5 6f);
b:.gpu.tensor.to (7 8 9f;10 11 12f);
.gpu.tensor.concat[0;(a;b)]  / (1 2 3f;4 5 6f;7 8 9f;10 11 12f)
.gpu.tensor.concat[1;(a;b)]  / (1 2 3 7 8 9f;4 5 6 10 11 12f)
```

`concat[axis;tensors]` joins a list of one or more tensors along `axis`, the way `,` joins along the first axis and `,'` along the second. `axis` is one q long atom, and negative indices count from the last axis. `tensors` is a general list, so a single tensor is passed as `enlist t`; a bare tensor is an error (`GPU_TYPE_UNSUPPORTED`). The tensors must have the same dtype (`KE`, `KF`, `KI` or `KJ`; nothing is promoted), the same rank (1 to 8) and the same extent on every axis except `axis`. Rank-zero tensors, an empty list, a mix of dtypes, a tensor on another device and disagreeing extents are errors.

The result is a new row-contiguous tensor that owns its storage, and the inputs are unchanged. Row-contiguous tensors and rank-2 transpose views are accepted, so `concat[0;enlist t]` is a row-contiguous copy of `t`, a transpose view or not. An input with extent zero along `axis` adds no elements, extent or names along it; its rank, its other extents and its other-axis names are still checked.

When every input with a nonzero extent along `axis` is named there, the result names that axis with the concatenated names. Otherwise that axis is unnamed. Every other axis is named when any input names it, and inputs that name it must agree (`GPU_AXIS_MISMATCH`), empty inputs included. Joined names are not checked for uniqueness: `concat[1;(t;t)]` of a tensor named `` `a`b `` names the axis `` `a`b`a`b ``, which `toTableAs` accepts and `toTable` rejects as duplicate names. The operation enqueues work on the device stream without a host-result synchronization.

### Taking and viewing rows of a tensor
```q
a:.gpu.tensor.to 1 2 3f;
.gpu.tensor.take[2;a]     / 1 2f
.gpu.tensor.take[-2;a]    / 2 3f
.gpu.tensor.take[7;a]     / 1 2 3 1 2 3 1f
.gpu.tensor.take[-7;a]    / 3 1 2 3 1 2 3f
m:.gpu.tensor.to (1 2 3f;4 5 6f;7 8 9f;10 11 12f);
.gpu.tensor.take[2;m]     / (1 2 3f;4 5 6f)
.gpu.tensor.take[-1;m]    / enlist 10 11 12f
.gpu.tensor.view[-2;m]    / (7 8 9f;10 11 12f), without a copy
```

`take[count;tensor]` is the tensor form of q's `n#x` over the leading axis: the first `count` entries or, when `count` is negative, the last `|count|`. For a matrix those entries are rows. Like q, it repeats the entries in a cycle when `|count|` exceeds the extent of the axis, and a negative count ends on the last entry (`-7#1 2 3` is `3 1 2 3 1 2 3`). `count` is one q long or int atom, and `tensor` is a tensor of rank 1 to 8 of any dtype (`KE`, `KF`, `KI` or `KJ`), which may be a transpose view. The result has the same dtype, device and rank, with `|count|` entries on the first axis and every other extent unchanged; `take[0;t]` is an empty tensor of that shape. Taking a nonzero count from an empty first axis fills the result with the dtype's q null, as q does for typed empty lists. q's long and int nulls, a rank-zero tensor and a result larger than the device can hold are errors (`GPU_INVALID_VALUE`, `GPU_TENSOR_SHAPE` and an allocation error); a `count` that is not a long or int atom, or a `tensor` that is not a tensor, reports `GPU_TYPE_UNSUPPORTED`.

`take` always returns a new row-contiguous tensor that owns its storage, so it never keeps the source alive and the source is unchanged. It needs free device memory for the result. The names of the first axis are repeated with the rows, so a cycled result can name an axis with repeated names (as for `concat`), and every other axis keeps its names.

`view[count;tensor]` returns the same first or last `|count|` rows without copying tensor data: the result shares the storage of `tensor`, with the same strides and an offset, and no kernel runs. It does not cycle, so a `count` with `|count|` above the extent of the first axis, and q's nulls, are errors (`GPU_INVALID_VALUE`). The result must be a layout the other tensor operators read: a row-contiguous tensor always qualifies, and so does a transpose view when `view` keeps every row of it (or one of its extents is 1). Only some of the rows of a transpose view are a strided window of the matrix, which is not readable, so `view` reports `GPU_TENSOR_SHAPE` there rather than copy; use `take`. A view keeps all of the storage of the source alive until it is released: `view[4;x]` of a very large `x` holds all of `x`. The names of the first axis are sliced to match, which copies a partial named axis on the host; every other axis keeps its names. Both operations enqueue work on the device stream without a host-result synchronization.

### Indexing a tensor with gather
```q
m:.gpu.tensor.to (1 2 3 4f;5 6 7 8f;9 10 11 12f);
.gpu.tensor.gather[1;m]            / 5 6 7 8f, an integer drops its axis
.gpu.tensor.gather[-1;m]           / 9 10 11 12f, counted from the end
.gpu.tensor.gather[(::;2);m]       / 3 7 11f, column 2 of every row
.gpu.tensor.gather[1 2;m]          / 7f, one integer per axis: a rank-zero tensor
.gpu.tensor.shape .gpu.tensor.gather[1 2;m]         / `long$(), the shape of a rank-zero tensor
.gpu.tensor.gather[enlist 2 0;m]   / (9 10 11 12f;1 2 3 4f), a list on axis 0
.gpu.tensor.gather[(::;3 3 0);m]   / (4 4 1f;8 8 5f;12 12 9f), repeats are allowed
.gpu.tensor.gather[(2 0;1 3);m]    / (10 12f;2 4f), the sub-grid of those rows and columns
.gpu.tensor.gather[(::;1010b);m]   / (1 3f;5 7f;9 11f), a mask keeps the true positions
.gpu.tensor.gather[enlist 101b;m]  / (1 2 3 4f;9 10 11 12f)
.gpu.tensor.gather[::;m]           / (1 2 3 4f;5 6 7 8f;9 10 11 12f), a copy
v:.gpu.tensor.to 1 2 3f;
.gpu.tensor.gather[enlist 2 0;v]   / 3 1f, the positions 2 and 0 of a vector
```

A range is a dictionary with the optional keys `` `start ``, `` `stop `` and `` `step ``; a bare dictionary is a range on axis 0:
```q
m:.gpu.tensor.to (1 2 3 4f;5 6 7 8f;9 10 11 12f);
.gpu.tensor.gather[`start`stop!1 3;m]                  / (5 6 7 8f;9 10 11 12f), rows 1 and 2
.gpu.tensor.gather[(::;`start`stop`step!1 100 2);m]    / (2 4f;6 8f;10 12f), stop clamped to 4
.gpu.tensor.gather[(::;`start`stop`step!0N 0N -2);m]   / (4 2f;8 6f;12 10f), 0N is not given
.gpu.tensor.gather[(::;(enlist `start)!enlist -2);m]   / (3 4f;7 8f;11 12f), the last two columns
.gpu.tensor.gather[(enlist `step)!enlist -1;m]         / (9 10 11 12f;5 6 7 8f;1 2 3 4f), reversed
```

On a tensor of higher rank every axis takes an item. `.gpu.tensor.from` downloads tensors of rank 0 to 2, so a result of higher rank is reshaped to a vector first:
```q
t:.gpu.tensor.reshape[2 3 4;.gpu.tensor.to "f"$til 24];     / t[i;j;k] is 12*i+4*j+k
.gpu.tensor.from .gpu.tensor.gather[(1;::;::);t]            / (12 13 14 15f;16 17 18 19f;20 21 22 23f)
.gpu.tensor.from .gpu.tensor.gather[(::;1;-1);t]            / 7 19f
.gpu.tensor.from .gpu.tensor.gather[(1;::;1 2);t]           / (13 14f;17 18f;21 22f), shape 3 2
r:.gpu.tensor.gather[(::;1 2;`start`stop!1 3);t];
.gpu.tensor.shape r                                         / 2 2 2
.gpu.tensor.from .gpu.tensor.reshape[enlist 8;r]            / 5 6 9 10 17 18 21 22f
```

`gather[spec;tensor]` selects elements of a tensor along every axis, as NumPy indexing does, with integers, lists, boolean masks and ranges, and copies them into a new row-contiguous tensor that owns its storage. `tensor` has rank 0 to 8 and any dtype (`KE`, `KF`, `KI` or `KJ`), and is row-contiguous, a rank-2 transpose view, which is read in place through its strides, or a [`view`](#taking-and-viewing-rows-of-a-tensor) of rows. The result has the dtype of `tensor`, and its elements are copied bit for bit: NaN payloads, `-0f` and q nulls survive. The source is unchanged, and the result does not keep it alive.

The selection is orthogonal: every axis is selected on its own, and the extent of the result on an axis is the number of positions chosen there. An axis chosen by an integer is dropped from the result, so a spec with one integer for every axis gives a rank-zero tensor.

`spec` is one of:

| `spec` | Selects |
|---|---|
| A general list | One item per axis, in order (see below). An axis with no item is kept whole, so fewer items than axes leave the last axes whole and `()` is the whole tensor. More items than axes signal `GPU_TENSOR_SHAPE`. |
| A long or int vector, such as `1 2 3` | One integer per axis, as in q's `x . 1 2 3`. q collapses `(1;2;3)` to this vector. |
| A long or int atom | An integer on axis 0. |
| `::` or `0#0` | The whole tensor, copied. |
| A dictionary | A range on axis 0. |
| A table | One range per row, one row per axis. |

A bare vector of positions is therefore one integer per axis, not a list of positions on axis 0: `gather[2 0;v]` of a vector `v` is two integers for one axis and signals `GPU_TENSOR_SHAPE`, and the list of positions is `enlist 2 0`.

Any other `spec` signals `GPU_TYPE_UNSUPPORTED`: a bare boolean vector (write `enlist 101b` for a mask on axis 0), a symbol, a float, an enumeration, or a function such as `neg`.

Each item of a general list selects along its axis:

| Item | Selects |
|---|---|
| A long or int atom | That position, and the axis is dropped. |
| A long or int vector, or `()` | Those positions, in that order. Repeats and an empty vector are allowed. |
| A boolean vector | The positions of its `1b` entries. Its length must equal the extent of the axis (`GPU_TENSOR_SHAPE`). |
| `::` | Every position. |
| A dictionary | A range of positions; `()!()` is every position. |

A position is an integer in `[-extent, extent)`, and a negative one counts from the end of the axis. A position outside that interval, a null (`0N` or `0Ni`) and any position on an axis of extent 0 signal `GPU_INVALID_VALUE`.

A range follows a Python slice. Its dictionary has any of the symbol keys `` `start ``, `` `stop `` and `` `step ``, and long or int atoms as values. A key that is missing, and a value of `0N`, `0Ni` or `::`, leave that bound unspecified: `start` and `stop` then default to the ends of the axis in the direction of the step, and `step` to 1. A negative `start` or `stop` counts from the end, both are clamped to the axis and never signal an error, and a negative `step` runs backwards, so `` `start`stop`step!0N 0N -1 `` is every position in reverse. A `step` of 0 and a key other than the three, or one given twice, signal `GPU_INVALID_VALUE`, and a key or a value of another type signals `GPU_TYPE_UNSUPPORTED`. A range of an empty axis, or one that selects nothing, is empty.

Two q behaviors shape how a spec is written:

- A dictionary with one key needs `enlist` on both sides, ``(enlist `step)!enlist -1``. `` `step!-1 `` and `` `step!enlist 2 `` are q enumerations (types `-20h` and `20h`), not dictionaries, and signal `GPU_TYPE_UNSUPPORTED`. `` `start`stop!1 3 `` is a dictionary.
- q turns a list of dictionaries with the same keys into a table, so `enlist d` and `(d1;d2)` are tables (type `98h`), not general lists. A table is accepted as one range per row, one row per axis, with each key a column, and a `0N` or `::` in a column leaves that bound unspecified. A list that mixes dictionaries with other items, such as `(d;3;::)`, or dictionaries with different keys, stays a general list and is read item by item.
```q
m:.gpu.tensor.to (1 2 3 4f;5 6 7 8f;9 10 11 12f);
d1:`start`stop!1 3;
d2:`start`stop!0 2;
type enlist d1                                   / 98h
type (d1;d2)                                     / 98h
type (d1;3;::)                                   / 0h
.gpu.tensor.gather[(d1;d2);m]   / (5 6f;9 10f), rows 1 and 2, columns 0 and 1
```

The names of the axes follow the selection. An axis chosen by an integer is dropped with its names, an axis kept whole keeps its names, and an axis selected by a range, a list or a mask is named by the names at the selected positions, which repeat when the positions do. The names of the source are unchanged.
```q
gt:.gpu.to ([]a:1 2 3f;b:10 20 30f;c:-1 0 1f);
f:.gpu.tensor.fromTable[`c`a;`features;gt];
(.gpu.tensor.meta .gpu.tensor.gather[(1 0 0;::);f])`axisNames   / (`a`c`c;`symbol$())
(.gpu.tensor.meta .gpu.tensor.gather[(::;1);f])`axisNames       / enlist `c`a
```

**Compared with NumPy.** Two index vectors give the sub-grid of those rows and columns, as `x[np.ix_([2, 0], [1, 3])]` does; NumPy's `x[[2, 0], [1, 3]]` pairs the indices instead, and has no counterpart here. NumPy moves the axes of a selection that has an integer and a list separated by a slice to the front: `x[1, :, [1, 2]]` of a tensor of shape `2 3 4` has shape `2 3`. `gather` keeps the axes in the order of the source, and gives shape `3 2` for `(1;::;1 2)`.

| NumPy | `gather` spec |
|---|---|
| `x[1]` | `1` |
| `x[-1]` | `-1` |
| `x[1, 2]` | `1 2` |
| `x[1:3]` | `` `start`stop!1 3 `` |
| `x[:, ::-1]` | ``(::;(enlist `step)!enlist -1)`` |
| `x[[2, 0]]` | `enlist 2 0` |
| `x[:, [0, 2]]` | `(::;0 2)` |
| `x[np.ix_([2, 0], [1, 3])]` | `(2 0;1 3)` |
| `x[:, mask]` | `(::;mask)`, with `mask` a boolean vector |

**Compared with q.** Indexing a q list of rows `x` at depth, `x . (2 0;1 3)`, is orthogonal too and gives the same sub-grid. In q a negative index is out of range and gives a null (`x[-1]` is null); `gather` counts negative positions from the end. Where q gives a null for a position beyond the end, `gather` signals `GPU_INVALID_VALUE`; ranges alone are forgiving, because they clamp like Python slices, whereas an integer or a list never does. q has no syntax for a range, which is why a range is a dictionary. The argument order differs from the column function [`.gpu.gather[data;idx]`](#gather), which takes the data first: `gather[spec;tensor]` takes the selector first, as `take` and `concat` do.

**Errors.** The first of these that applies is reported, in this order: the `tensor`, the form of `spec`, the number of items against the number of axes, each axis in turn (the type of its item, then the length of a mask, then its positions, steps and keys), the size of the result, the names of the result and the allocation.

| Error | Cause |
|---|---|
| `GPU_TYPE_UNSUPPORTED` | `tensor` is not a tensor. A `spec` or an item of a kind the tables above do not list, or a range whose keys are not symbols or whose values are not long or int atoms. |
| `GPU_WRONG_DEVICE` | `tensor` is on another device. |
| `GPU_TENSOR_SHAPE` | `spec` has more items than the tensor has axes. A mask whose length differs from the extent of its axis. The element count of the result, or the number of positions in its lists and masks, overflows. |
| `GPU_INVALID_VALUE` | A position outside its axis, a null position, a `step` of 0, or a range key that is unknown or repeated. |
| `GPU_HOST_ALLOC_FAILED` | The names of a selected axis would take more than 2^31 - 1 bytes of host memory. |

A result that does not fit in device memory reports the allocation error, as for the other tensor operations. `gather` needs free device memory for the result, plus 8 bytes for each position of a list or a mask while the call runs.

A spec made only of integers, ranges and `::` needs no index data: the call enqueues its copy on the device stream without a host-result synchronization, so it chains into further tensor operations. A spec with a list or a mask first builds the selected positions on the host and uploads them, so its cost grows with the number of positions. The names of a partly selected named axis are built on the host.

Not implemented: index tensors that stay on the device (a list or a mask is a q vector, uploaded on every call), zero-copy views for plain slices (`view` returns the leading rows without a copy), and assignment into a selection.

### Principal component analysis
```q
x:.gpu.tensor.to (2 0 1f;0 2 1f;3 1 2f;1 3 2f);
p:.gpu.tensor.pca[2;x]; / eigh on this small Gram, centered, sample variance
p`explainedVariance
.gpu.tensor.mmu[.gpu.tensor.sub[x;p`mean];p`components] / resident projection
```

Input rows are observations and columns are features. PCA accepts nonempty int, long, real, and float rank-2 tensors (`KI`, `KJ`, `KE`, `KF`), including transpose and offset views, on the current device. It does not modify the input. `KE` computes natively in float32 and returns every numerical field as `KE`; `KF` stays float64. Integer inputs are converted before arithmetic and return `KF` fields. Long values above `2^53` may round. Integer nulls become floating nulls and are rejected as nonfinite; other integers, including the reserved integer infinity values, are cast numerically. Configuration is a long `k` or a dictionary with unique symbol keys:

| Option | Default | Contract |
|---|---|---|
| `k` | Required | Long, `1 <= k <= min(features, observations-center)` |
| `method` | `eigh` up to Gram order 512, else `full` | Symbol `full` or `eigh`; unrequested it routes to `eigh` for small Gram orders, where the SVD path is far slower for the same spectrum |
| `center` | `1b` | Boolean; subtract column means |
| `ddof` | `1` | Long, `0 <= ddof < observations` |
| `returnComponents` | `1b` | Boolean; include feature directions |
| `returnScores` | `1b` | Boolean; include projected observations |

Unknown/duplicate keys and `auto`/`random` methods are errors. A one-observation input requires `center:0b`, `ddof:0`, and `k:1`. Without centering, the mean output is a zero vector; PCA does not standardize feature variances.

| Result field | GPU tensor shape | Meaning |
|---|---|---|
| `components` | `[features,k]` | Feature directions in descending singular-value order |
| `scores` | `[observations,k]` | `(input-mean) mmu components` |
| `mean` | `[features]` | Subtracted means, or zeros |
| `singularValues` | `[k]` | Descending singular values |
| `explainedVariance` | `[k]` | `singularValues^2/(observations-ddof)` |
| `explainedRatio` | `[k]` | Squared singular values divided by all optionally-centered input energy |

The dictionary also contains `method`, a q symbol. Disabled components/scores are `::`; all other numerical fields remain GPU tensors of the floating compute dtype. Feature names label the first components axis and mean; observation names label the first scores axis. Component-index axes are unnamed. Download explicitly with `.gpu.tensor.from`. Signs and bases within repeated singular subspaces are not unique.

`full` uses thin full SVD and permits zero singular values. Zero input energy produces zero explained ratios. `eigh` forms the smaller Gram matrix, selecting its leading eigenpairs and lifting feature directions for wide inputs. Its Gram order is limited to 32768. This saves substantial storage for many-feature inputs, but forming a Gram matrix squares the condition number. Small Gram orders (up to 512) form the Gram, the centered energy, and narrow-input scores directly over the raw input in one pass, allocating no centered copy; larger orders form a centered copy and retain the symmetric rank-k update in float64. Float32 has lower accuracy and a stricter numerical-rank cutoff; use float64 or retry explicitly with `full` when appropriate. No implicit precision upgrade occurs.

For `eigh`, the numerical-rank cutoff is `8*epsilon*max(observations,features)*max(largestEigenvalue,0)`, where `epsilon` is `FLT_EPSILON` for `KE` and `DBL_EPSILON` for `KF` (including promoted integers). Requested eigenvalues within the negative-to-positive cutoff interval raise `GPU_TENSOR_PCA_RANK`; more negative or nonfinite spectrum values raise `GPU_TENSOR_PCA_SPECTRUM`. Column means use the typed `avg` reduction, so integer accumulation promotes to float64 before summing. Input conversion is fused with centering into the solver matrix where one is formed; no separate promoted input copy is allocated, and the small-order eigh path forms no solver matrix at all. Mean accumulation, centered energy, factorization, projection, and spectrum outputs use the compute dtype. Float32 mean or energy overflow therefore reports `GPU_TENSOR_NONFINITE`, even if the float64 calculation would succeed. PCA rejects nonfinite input or intermediate energy with `GPU_TENSOR_NONFINITE`, and failed convergence raises `GPU_TENSOR_PCA_CONVERGENCE`. These checks require stream synchronization even though output arrays remain resident. PCA also holds the device's linear-algebra lock until it completes, so concurrent `mmu`, `inv`, `chol`, `lsq` and `pca` calls from other threads on that device wait. For a requested method no fallback occurs inside the library: a caller that wants one retries with `method:full` when `GPU_TENSOR_PCA_RANK` is raised, and lets every other error through. The unrequested-method routing above instead falls back to `full` once on its own when the Gram spectrum is rank-deficient, so zero-energy data still returns zero outputs.

### Tensor/table conversion

_Pack GPU table columns into a tensor, or scatter a tensor into GPU table columns_
```syntax
.gpu.tensor.fromTable[columns;orientation;table]
.gpu.tensor.toTable[orientation;tensor]
.gpu.tensor.toTableAs[names;orientation;tensor]
```

Where:

- `table` is an unkeyed table;
- `columns` is `::`, a symbol atom, or a nonempty symbol vector;
- `orientation` is `::` (the default, equivalent to `` `features ``), `` `features ``, or `` `records ``; and
- `names` is a symbol atom for a single output column, or a unique symbol vector with one name per output column.

Every selected input column must be a flat GPU-resident `KI`, `KJ`, `KE`, or `KF` vector on the current device. All selected columns must have the same exact type and row count; selection order is preserved. There is no implicit promotion. Unselected columns do not participate in validation, so a mixed-residency or mixed-type table can be converted when the selected columns satisfy the contract.

| Orientation | Tensor shape | Tensor-to-table columns |
|---|---:|---|
| `` `features `` | `[columnCount,rowCount]` | rows of the tensor (axis 0) |
| `` `records `` | `[rowCount,columnCount]` | columns of the tensor (axis 1) |

Both `fromTable` orientations produce canonical row-major tensors. Zero rows are supported; at least one column is required.

The two orientations differ only in physical layout: `` `records `` is logically identical to `.gpu.tensor.transpose` of the `` `features `` result, but is physically packed record-major rather than a zero-copy view. Prefer `` `features `` (or `::`) with `transpose` where a view suffices — `mmu` and `toTable` consume transpose views directly — and use `` `records `` when a consumer needs the packed `[rowCount,columnCount]` buffer itself.
```q
q)gt:.gpu.to ([]a:1 2 3f;b:10 20 30f;c:-1 0 1f)
q)features:.gpu.tensor.fromTable[`c`a;`features;gt]
q).gpu.tensor.shape features
2 3
q)features
-1 0 1
1  2 3
q)(.gpu.tensor.meta features)`axisNames
`c`a
`symbol$()
q).gpu.tensor.toTable[`features;features]
c  a
----
-1 1
0  2
1  3

q)records:.gpu.tensor.fromTable[`c`a;`records;gt]
q).gpu.tensor.shape records
3 2
q)records
-1 1
0  2
1  3
```

`fromTable` records selected table column names on the feature axis. `toTable` reuses those names and reports `GPU_INVALID_VALUE` if that axis is unnamed, the wrong length, or contains duplicates. Use `toTableAs` to supply output names explicitly; the override does not mutate tensor metadata.
```q
q)plain:.gpu.tensor.to (1 2f;3 4f;5 6f)
q).gpu.tensor.toTableAs[`bid`ask;`records;plain]
bid ask
-------
1   2
3   4
5   6
```

The bridge performs asynchronous device-to-device copies: it does not materialize column values on the host. The returned tensor or table owns independent device storage and does not alias its source. Rank-2 row-contiguous tensors and transpose views are supported; arbitrary strided views are rejected.

Axis names follow tensor semantics:

- `transpose` swaps the two axes;
- a shape-identical `reshape` preserves names, while any changed rank or extent clears them;
- `inv` swaps domain and codomain axes;
- `mmu` propagates its uncontracted axes;
- `lsq` propagates the target-row and predictor-row axes;
- `concat` joins the names of its concatenation axis when every input names it, and keeps the names of every other axis, which must agree between the inputs that name them;
- `asc` and `desc` keep the names of every axis except the sorted one, whose names they clear; and
- `gather` drops the names of an axis that an integer selects, keeps those of an axis it keeps whole, and names every other axis it selects from with the names at the selected positions.

When both axes of an `mmu` or `lsq` contraction are named, the complete symbol vectors must match in value and order. A mismatch reports `GPU_AXIS_MISMATCH`. If either side is unnamed, contraction is positional, preserving the behavior of tensors created with `.gpu.tensor.to`.

> **Note**
>
> Table columns and tensors are separate allocation families. The bridge avoids host copies, but
> it is not zero-copy and its results do not share device buffers with their inputs.
>
### Random tensors

_Generate tensor values directly in device memory with cuRAND_

A random generator is a separate stateful foreign object. Generated results are ordinary tensors and retain no link to their generator. Rank-2 results can immediately enter the table bridge; nonempty rank-1 or rank-2 results of all four dtypes can enter `mmu`, and rank-2 results can enter `inv`, `chol`, `lsq`, and PCA subject to their shape requirements. Generator state is explicit and independent of q's `\\S` seed.

The short constructor creates a Philox generator on the current device:
```q
q)r:.gpu.tensor.random.create 42j
q).gpu.tensor.random.meta r
algorithm | `philox
seed      | 42
offset    | 0
dimensions| ::
ordering  | `best
device    | 0
```

The dictionary form exposes the creation parameters:
```q
r:.gpu.tensor.random.create
  `algorithm`seed`offset`ordering!(`philox;42j;0j;`best)
s:.gpu.tensor.random.create
  `algorithm`dimensions`offset!(`scrambledSobol64;8j;0j)
```

Supported algorithms are `philox`, `sobol32`, `scrambledSobol32`, `sobol64`, and `scrambledSobol64`. Philox dictionaries require a seed and accept `best` or `legacy` ordering. Sobol generators require 1 to 20,000 dimensions and do not accept a seed or an ordering (`meta` reports their ordering as `quasiDefault`); their shape must always be `dimensions points`, matching cuRAND's dimension-major layout.

For Philox, `shape` is either a long atom (rank-1 shorthand) or a long vector of zero, one, or two items. Zero-sized results do not advance generator state. `reset` synchronizes the generator's device stream and restores its creation state.

Distribution results are:

| Function | Result |
|---|---|
| `bits` | `KI` for Philox/Sobol32 and `KJ` for Sobol64; signed q values preserve the raw bit patterns |
| `uniform` | `KE` or `KF` values in `[0,1)` |
| `normal` | `KE` or `KF` values with finite mean and nonnegative finite standard deviation |
| `lognormal` | `KE` or `KF`; parameters describe the underlying normal distribution |
| `poisson` | `KJ`, with `0 < lambda <= 400000` |
| `randint` | Philox-only `KJ` values in the half-open interval `[low,high)` |

For floating distributions, `::` and ``float`` select `KF`; ``real`` selects `KE`. `randint` combines 64 source bits and uses multiply-high range reduction. It is asynchronous and has at most one extra source value per bucket out of 2<sup>64</sup>; it is not a cryptographic or exact-rejection generator.

Generation is ordered on the module's existing per-device CUDA stream. Assigning a generator to a second q name aliases its state; call `create` again for independent generator state. With `best` ordering, exact replay is scoped to the same generator configuration, call sequence, GPU, and CUDA/cuRAND stack. Use `legacy` when stable Philox output ordering across cuRAND releases is more important than allowing future ordering improvements. The generator's creating device must be current for generation and reset; using it while another device is current reports `GPU_WRONG_DEVICE` without advancing its state.


## to

_Send data to the GPU_
```syntax
.gpu.to x
```

Where `x` is any CPU, GPU or mixed-resident object. Returns a copy of `x` with any non-atom moved to the GPU.

> **Note — All attributes are preserved on `.gpu.to`, while all but `` `s `` are lost on `.gpu.from`.**
>
>
For supported vectors with the parted (`` `p ``) attribute, `.gpu.to` validates that each distinct value occupies one contiguous run and builds an index for reuse by [`.gpu.bin`](#bin), [`.gpu.aj`](#aj), single-key [`.gpu.wj`](#wj) and [`.gpu.wj1`](#wj1), and single-key [`.gpu.lj`](#lj) and [`.gpu.ij`](#ij). Index construction is part of the transfer cost. Empty vectors require no index.

If the data does not satisfy the parted condition, `.gpu.to` clears the attribute on the GPU copy and retains no parted index. Subsequent as-of joins use the temporary sorted-index path, and window joins with equality keys reject the table.

For supported vectors with the unique (`` `u ``) attribute, `.gpu.to` builds the same index, with one row per value, and the GPU copy keeps the `` `u `` attribute. Single-key [`.gpu.lj`](#lj) and [`.gpu.ij`](#ij), [`.gpu.aj`](#aj) and [`.gpu.bin`](#bin) reuse it. If a value is repeated, `.gpu.to` clears the attribute on the GPU copy and retains no index. Vectors of types the index does not support, such as nested lists, keep the attribute without an index. Appending to the GPU vector in place clears the attribute and releases the index, because the appended values may repeat a key.
```q
q).gpu.to 10
10
q).gpu.to 10 20 30         / a virtual object; display fetches a copy
10 20 30
q).gpu.type .gpu.to 10 20 30
7h
q).gpu.to `a`b!(10 20;30 40)
a| 10 20
b| 30 40
q).gpu.meta .gpu.to flip `a`b!(10 20;30 40)
c| t f a r
-| ---------
a| j     gpu
b| j     gpu
q).gpu.from .gpu.to flip `a`b!(10 20;30 40)
a  b
-----
10 30
20 40
```

### xto

_Send specific columns/keys to the GPU_
```syntax
.gpu.xto[x;y]
```

Where `x` is a symbol atom/list specifying keys/columns of a dictionary/table `y`, returns `y` with specified non-atoms moved to the GPU.
```q
q).gpu.meta .gpu.xto[`a`c] flip `a`b!(10 20;30 40)   / names not in y are ignored
c| t f a r
-| ---------
a| j     gpu
b| j     cpu
q).gpu.meta .gpu.xto[`a] flip `a`b!(10 20;30 40)
c| t f a r
-| ---------
a| j     gpu
b| j     cpu
```

## type

_Type of a GPU-resident object_
```syntax
.gpu.type x
```

Where `x` is any CPU or GPU-resident object, returns its type as short.

> **Warning — Vanilla `type` returns `112h` for GPU-resident virtual objects regardless of their true underlying type.**
>
>
```q
q)A:.gpu.to a:1 2 3
q).gpu.type A
7h
q).gpu.type a
7h
q)type a
7h
q)type A
112h

q)B:.gpu.to b:1 2 3f
q).gpu.type B
9h
q)type B
112h
```

## update

_Functional update_

The GPU module provides an `update` function that mirrors the [functional-update](https://code.kx.com/q/basics/funsql/) API `![t;c;b;a]`, except that `t` is a table whose columns reside on the GPU.
```syntax
.gpu.update[t;c;b;a]
```

`.gpu.update` takes the same arguments as [`.gpu.select`](#select) and supports the same operations.
Instead of returning the selected columns, `.gpu.update` returns the full table, with the columns named in `a` updated, or added if they do not already exist.
Where a `where` phrase is given, only the matching rows are modified.

> **Note — q's `update ... from T` and `delete ... from T` (and `![T;c;b;a]`) are not supported on a GPU table; use `.gpu.update`.**
>
>
```q
q)t:([]sym:`AAPL`MSFT`AAPL`MSFT`AAPL;p:150 300 152 298 155;size:100 200 150 300 250)
q)T:.gpu.to t

q)/ double size where sym = `AAPL
q).gpu.update[T;enlist(=;`sym;enlist`AAPL);0b;enlist[`size]!enlist(*;2;`size)]
sym  p   size
-------------
AAPL 150 200
MSFT 300 200
AAPL 152 300
MSFT 298 300
AAPL 155 500
```

Refer to [`.gpu.select`](#select) for the argument details and the list of supported operations.

> **Warning — Type changes are only allowed on a full-column replace**
>
> Replacing a whole column may change its type.
> When a `where` phrase means only some rows are written, the new values must have the same type as the existing column; safe widening (for example, adding an `int` to produce values for a `long` column) is accepted.
> A mismatch is rejected with `GPU_INVALID_STATEMENT`.
>
> **Warning — Symbol and enum representations must match**
>
> Writing into an enum column from an enum of a different domain, or into a plain symbol column from an enum column, is rejected with `GPU_NOT_IMPLEMENTED`, though this is not always checked.
>
> **Warning — `iasc` on plain symbol columns may differ from q**
>
> As in `.gpu.select`, `iasc` over a plain (non-enumerated) symbol column orders by internal symbol pointers rather than the symbol text, so the result may differ from q.
> Use an enumerated symbol column instead.
>
## ver

_Module Version String_
```syntax
q).gpu.ver
"gpulib version: 2.0.0-ada-cuda13.1"
```

Returns the version string of the module.
