# Runtime Dispatch

> StringZilla for Python

StringZilla detects the running CPU's SIMD features and routes every kernel to the fastest capability both the CPU and this build have, without recompilation.
Capabilities are `sz.Capability` flags, one member per CPU capability, like `sz.Capability.HASWELL` or `sz.Capability.NEON`; GPU bits sit above every member, so a GPU mask prints as a number.

The producers report capabilities for the CPU, or for one GPU by its runtime's ordinal, or make a stream on that GPU, and are the only calls taking an ordinal.

- `sz.__version__` — the package version string.
- `sz.cpu_capabilities_detected()`, `sz.cuda_capabilities_detected(ordinal)` — what the device can execute, whether or not its kernels were compiled in.
- `sz.cpu_capabilities_compiled()`, `sz.cuda_capabilities_compiled()` — what this build holds kernels for, whether or not the device runs them.
- `sz.cpu_capabilities_enabled()`, `sz.cuda_capabilities_enabled(ordinal)` — what calls dispatch with, both of the above; on the CPU it always has `SERIAL`.
- `sz.cuda_count_devices()` — the number of CUDA devices; it raises `RuntimeError` when none answers, as when this build lacks its kernels, and so does an ordinal past the last device.
- `sz.cpu_configure_thread(capabilities)` — prepare the calling thread for the kernels of `capabilities`; call it on every thread that runs kernels.
- `sz.cuda_stream_init(ordinal)`, `sz.cuda_stream_free(stream)` — make a stream on one device, as the integer every `stream=` takes, and free it once nothing queued on it remains.
- `sz.synchronize(capabilities, stream=None)` — wait for one stream of the vendor `capabilities` names, or its default one.

ROCm and Metal have the same `rocm_` and `metal_` producers.
Every function that runs kernels also takes a keyword-only `capabilities=`, narrowing that one call and leaving every other call alone.
The keyword keeps no serial fallback, so a mask with no kernel for the call raises `LookupError`.

```python
import stringzilla as sz

sz.cpu_capabilities_enabled()                                 # e.g. <Capability.SERIAL|NEON|NEONAES|NEONSHA: 449>
sz.find("haystack", "st", capabilities=sz.Capability.SERIAL)  # one call on the scalar kernel
sz.cpu_configure_thread(sz.cpu_capabilities_enabled())        # on a thread that runs kernels
[sz.cuda_capabilities_enabled(ordinal) for ordinal in range(sz.cuda_count_devices())]  # raises without CUDA
```

Engines and the stateful objects, like `Hasher`, `Sha256` and the AES keys, dispatch every call with the capabilities they were constructed with.

`import stringzilla`

## Capability

```python
class Capability(enum.IntFlag)
```

- `SERIAL`
- `WESTMERE`
- `GOLDMONT`
- `HASWELL`
- `SKYLAKE`
- `ICELAKE`
- `NEON`
- `NEONAES`
- `NEONSHA`
- `SVE`
- `SVE2`
- `SVE2AES`
- `RVV`
- `RVVCRYPTO`
- `V128`
- `V128RELAXED`
- `LOONGSONASX`
- `POWERVSX`

## cpu_capabilities_detected

Get the capabilities this CPU can execute.

```python
def cpu_capabilities_detected() -> Capability
```

Detected from CPUID or HWCAP. Says nothing about whether the kernels were compiled in, which
[`cpu_capabilities_compiled`](#cpu_capabilities_compiled) reports.

## cpu_capabilities_compiled

Get the CPU capabilities whose kernels were compiled into this binary.

```python
def cpu_capabilities_compiled() -> Capability
```

Decided at build time. Independent of the hardware: a binary built with a broken probe toolchain
still detects this machine's full set while containing no SIMD kernels at all, which is what
makes a scalar build hard to spot.

## cpu_capabilities_enabled

Get the CPU capabilities kernels run with, unless a call passes `capabilities=`.

```python
def cpu_capabilities_enabled() -> Capability
```

It is `cpu_capabilities_detected() & cpu_capabilities_compiled()`, and always has SERIAL.

## cpu_configure_thread

Prepare the calling thread for kernels of `capabilities`.

```python
def cpu_configure_thread(capabilities: Capability, /) -> None
```

Call it on every thread that runs kernels, before the first of them.

- `capabilities`: The mask kernels on this thread run with.

## *_count_devices

Count the devices of one GPU vendor this process sees: [`cuda_count_devices`](#cuda_count_devices), and the `rocm_` and `metal_` ones alike.

```python
def cuda_count_devices() -> int
```

- `cuda_count_devices`
- `rocm_count_devices`
- `metal_count_devices`

Raises: - `RuntimeError`: No device of that vendor answers, or this build lacks its kernels.

## *_capabilities_detected

Get the capabilities a GPU can execute, by its runtime's ordinal, whether or not the kernels were compiled in: [`cuda_capabilities_detected`](#cuda_capabilities_detected), and the `rocm_` and `metal_` ones alike.

```python
def cuda_capabilities_detected(ordinal: int, /) -> Capability
```

- `cuda_capabilities_detected`
- `rocm_capabilities_detected`
- `metal_capabilities_detected`

Raises: - `RuntimeError`: No device with that ordinal answers.

## *_capabilities_compiled

Get the capabilities of one GPU vendor whose kernels were compiled into this binary, whether or not a device runs them: [`cuda_capabilities_compiled`](#cuda_capabilities_compiled), and the `rocm_` and `metal_` ones alike.

```python
def cuda_capabilities_compiled() -> Capability
```

- `cuda_capabilities_compiled`
- `rocm_capabilities_compiled`
- `metal_capabilities_compiled`

## *_capabilities_enabled

Get the capabilities kernels run with on a GPU, by its runtime's ordinal, both detected and compiled: [`cuda_capabilities_enabled`](#cuda_capabilities_enabled), and the `rocm_` and `metal_` ones alike.

```python
def cuda_capabilities_enabled(ordinal: int, /) -> Capability
```

- `cuda_capabilities_enabled`
- `rocm_capabilities_enabled`
- `metal_capabilities_enabled`

Engines, [`Strings.copy`](https://www.unum.cloud/docs/stringzilla/python/hashing-and-checksums.md#copy) and [`synchronize`](#synchronize) take this mask, which names the vendor, and a stream, which names the device.

Raises: - `RuntimeError`: No device with that ordinal answers.

## *_stream_init

Create a stream on a GPU, by its runtime's ordinal, which names that device to the engines, [`Strings.copy`](https://www.unum.cloud/docs/stringzilla/python/hashing-and-checksums.md#copy) and [`synchronize`](#synchronize) it is passed to: [`cuda_stream_init`](#cuda_stream_init), and the `rocm_` and `metal_` ones alike.

```python
def cuda_stream_init(ordinal: int, /) -> int
```

- `cuda_stream_init`
- `rocm_stream_init`
- `metal_stream_init`

Free it with the matching `*_stream_free` once its work is synchronized.

Raises: - `RuntimeError`: No device with that ordinal answers, or this build lacks its vendor.

## *_stream_free

Free a stream [`cuda_stream_init`](#cuda_stream_init) made, and the `rocm_` and `metal_` ones alike, once [`synchronize`](#synchronize) joined whatever was queued on it; nothing may use the stream afterwards.

```python
def cuda_stream_free(stream: int, /) -> None
```

- `cuda_stream_free`
- `rocm_stream_free`
- `metal_stream_free`

## synchronize

Wait for everything enqueued on one stream, releasing the GIL meanwhile.

```python
def synchronize(
    capabilities: Capability,
    stream: int | None = None,
) -> None
```

A GPU engine's verbs enqueue and return, so their outputs are read, and their inputs freed, only
after this returns. Returns at once for CPU capabilities.

- `capabilities`: A producer's mask, which names the vendor.
- `stream`: A stream of that vendor as an integer, or None for the default.

Raises: - `BufferError`: The stream belongs to another device.

- `RuntimeError`: A launch on the stream failed, or the device does not answer.

