Rust · Runtime Dispatch

Runtime Dispatch

use numkong::Capabilities;use numkong::Capability;use numkong::Scope;use numkong::Stream; 8.0.0-rc.1 · b1f866ab

Capabilities answers for the CPU and each GPU group through its associated functions, and Capabilities::configure_thread enables CPU-specific acceleration features such as Intel AMX. It must be called once per thread before using AMX operations.

use numkong::{Capabilities, Capability};

let enabled = Capabilities::cpu_enabled();
enabled.configure_thread().unwrap();

println!("dispatching to {enabled}"); // like "serial,neon,neonhalf"
if enabled.contains(Capability::SapphireAmx) {
    println!("AMX enabled");
}

for ordinal in 0..Capabilities::cuda_count_devices().unwrap() {
    println!("CUDA device {ordinal} runs {}", Capabilities::cuda_enabled(ordinal).unwrap()); // like "cuda,ampere,hopper"
}

Each group reports a Capabilities set of Capability bits along two independent axes, plus the set dispatch uses:

  • cpu_detected(), cuda_detected(ordinal) and their ROCm and Metal kin: what the device can execute, from CPUID or HWCAP on the CPU and from the runtime on a GPU
  • cpu_compiled(), cuda_compiled() and their kin: what this binary contains, from the probes at build time
  • cpu_enabled(), cuda_enabled(ordinal) and their kin: what dispatch runs within, i.e. both axes at once

Reach for the _enabled ones unless you specifically mean one of the raw axes. cpu_detected() describes the machine and says nothing about whether a kernel was compiled in, so a build whose ISA probes failed still reports your CPU’s full feature set while containing no SIMD kernels at all. The crate keeps no process state: every CPU kernel call passes Capabilities::CPUS, and the library clamps it to the CPU capabilities it settles as it loads. No verb allocates: a GPU verb takes any memory its device reaches, which the runtime checks per call. Stream::new(capabilities, ordinal) makes a stream on a device, Stream::default(capabilities) names the vendor’s default, and stream.synchronize() waits for it. UnifiedAllocator::new(&stream) allocates memory the host and that stream’s device both address, the only unified memory the crate hands out. stream.scope(|scope| ...) runs the safe _on verbs, each queueing on that stream with its capabilities, and joins it before anything they borrow is released. Capabilities::stream_init_cuda(ordinal) and Capabilities::stream_free_cuda(stream) stay as the unsafe raw handles, with rocm_ and metal_ twins.

Call configure_thread at the start of every thread that will use AMX operations. In a thread-pool setting, each worker thread needs its own call. The function is idempotent and cheap to call more than once on the same thread.

API

StructCapabilitiesA set of capabilities, printed as comma-separated names like serial,haswell.30
capabilities.rs:376 ↗
pub struct Capabilities(_);

Examples

use numkong::{Capabilities, Capability};

let enabled = Capabilities::cpu_enabled();
enabled.configure_thread()?;
println!("dispatching to {enabled}");
if enabled.contains(Capability::SapphireAmx) {
    println!("AMX is enabled");
}
for ordinal in 0..Capabilities::cuda_count_devices()? {
    println!("CUDA device {ordinal} runs {}", Capabilities::cuda_enabled(ordinal)?);
}

Implements

Clone, Copy, Debug, Default, StructuralPartialEq, PartialEq, Eq, Hash, From<Capability>, BitOr<Capability>, BitOr, Display
ANYEvery capability, C's nk_cap_any_k.
capabilities.rs:380 ↗
pub const ANY: Self = _;
CPUSEvery CPU capability, the bits below the first GPU vendor's, C's nk_cap_cpus_k.
capabilities.rs:384 ↗
pub const CPUS: Self = _;

Calls without a mask of their own pass it, as dispatch clamps it to what this CPU runs.

GPUSEvery GPU capability, which the CPU never detects or enables, C's nk_cap_gpus_k.
capabilities.rs:387 ↗
pub const GPUS: Self = _;
bitsThe raw nk_capability_t mask.
capabilities.rs:405 ↗
pub const fn bits(self) -> u64
containsWhether capability is in this set.
capabilities.rs:408 ↗
pub const fn contains(self, capability: Capability) -> bool
withoutThis set less capability, like enabled.without(Capability::Skylake).
capabilities.rs:411 ↗
pub const fn without(self, capability: Capability) -> Self
iterThe capabilities in this set, in bit order.
capabilities.rs:416 ↗
pub fn iter(self) -> impl Iterator<Item = Capability>
cpu_detectedCapabilities this CPU supports, whether or not their kernels were compiled in.
capabilities.rs:431 ↗
pub fn cpu_detected() -> Self
cpu_compiledCPU capabilities whose kernels were compiled in, whether or not this CPU supports them.
capabilities.rs:439 ↗
pub fn cpu_compiled() -> Self
cpu_enabledThe CPU capabilities every kernel call runs within: Capabilities::cpu_detected & Capabilities::cpu_compiled, always with Capability::Serial.
capabilities.rs:447 ↗
pub fn cpu_enabled() -> Self
configure_threadSets up the calling thread for the CPU kernels in this set, usually [Capabilities::cpu_enabled]: AMX tile permission on x86 Linux, fused BF16 dots on Arm.
capabilities.rs:456 ↗
pub fn configure_thread(self) -> Result<(), Error>

Call it once per thread before using those kernels; it is idempotent.

cuda_count_devicesHow many CUDA devices this process sees, zero without the runtime or without its kernels.
capabilities.rs:459 ↗
pub fn cuda_count_devices() -> Result<usize, Error>
cuda_detectedCapabilities CUDA device ordinal supports, by the runtime's own numbering, whether or not their kernels were compiled in.
capabilities.rs:472 ↗
pub fn cuda_detected(ordinal: usize) -> Result<Self, Error>
cuda_compiledCUDA capabilities whose kernels were compiled in.
capabilities.rs:479 ↗
pub fn cuda_compiled() -> Self
cuda_enabledThe mask CUDA device ordinal dispatches with: Capabilities::cuda_detected & Capabilities::cuda_compiled.
capabilities.rs:487 ↗
pub fn cuda_enabled(ordinal: usize) -> Result<Self, Error>
stream_init_cudaA new cudaStream_t on CUDA device ordinal, which names that device to every call it is passed to; fails with Status::MissingGpu past the last device or where this build lacks CUDA.
capabilities.rs:499 ↗
pub unsafe fn stream_init_cuda(ordinal: usize) -> Result<*mut c_void, Error>

Safety

The caller owns the stream and frees it once, through Capabilities::stream_free_cuda.

stream_free_cudaFrees a stream Capabilities::stream_init_cuda made.
capabilities.rs:510 ↗
pub unsafe fn stream_free_cuda(stream: *mut c_void) -> Result<(), Error>

Safety

stream must come from Capabilities::stream_init_cuda and not be freed yet, and nothing may use it afterwards, so whatever was queued on it is synchronized first.

rocm_count_devicesCapabilities::cuda_count_devices, for ROCm.
capabilities.rs:515 ↗
pub fn rocm_count_devices() -> Result<usize, Error>
rocm_detectedCapabilities::cuda_detected, for ROCm.
capabilities.rs:526 ↗
pub fn rocm_detected(ordinal: usize) -> Result<Self, Error>
rocm_compiledCapabilities::cuda_compiled, for ROCm.
capabilities.rs:533 ↗
pub fn rocm_compiled() -> Self
rocm_enabledCapabilities::cuda_enabled, for ROCm.
capabilities.rs:540 ↗
pub fn rocm_enabled(ordinal: usize) -> Result<Self, Error>
stream_init_rocmCapabilities::stream_init_cuda, for ROCm, making a hipStream_t.
capabilities.rs:550 ↗
pub unsafe fn stream_init_rocm(ordinal: usize) -> Result<*mut c_void, Error>

Safety

The caller owns the stream and frees it once, through Capabilities::stream_free_rocm.

stream_free_rocmCapabilities::stream_free_cuda, for ROCm.
capabilities.rs:560 ↗
pub unsafe fn stream_free_rocm(stream: *mut c_void) -> Result<(), Error>

Safety

stream must come from Capabilities::stream_init_rocm, on the same terms.

metal_count_devicesCapabilities::cuda_count_devices, for Metal, whose devices count in system order.
capabilities.rs:565 ↗
pub fn metal_count_devices() -> Result<usize, Error>
metal_detectedCapabilities::cuda_detected, for Metal.
capabilities.rs:576 ↗
pub fn metal_detected(ordinal: usize) -> Result<Self, Error>
metal_compiledCapabilities::cuda_compiled, for Metal.
capabilities.rs:583 ↗
pub fn metal_compiled() -> Self
metal_enabledCapabilities::cuda_enabled, for Metal.
capabilities.rs:590 ↗
pub fn metal_enabled(ordinal: usize) -> Result<Self, Error>
stream_init_metalCapabilities::stream_init_cuda, for Metal, making an id<MTLCommandQueue>.
capabilities.rs:600 ↗
pub unsafe fn stream_init_metal(ordinal: usize) -> Result<*mut c_void, Error>

Safety

The caller owns the stream and frees it once, through Capabilities::stream_free_metal.

stream_free_metalCapabilities::stream_free_cuda, for Metal.
capabilities.rs:610 ↗
pub unsafe fn stream_free_metal(stream: *mut c_void) -> Result<(), Error>

Safety

stream must come from Capabilities::stream_init_metal, on the same terms.

synchronizeWaits for everything queued on stream, a stream of this mask's group, null for its default, and null on the CPU.
capabilities.rs:620 ↗
pub unsafe fn synchronize(self, stream: *mut c_void) -> Result<(), Error>

Safety

stream must be null or a live cudaStream_t, hipStream_t or id<MTLCommandQueue> of this mask's group.

EnumCapabilityOne capability, numbered like the C nk_cap_<capability>_k bits: each capability group in a contiguous run, ascending by dispatch preference, with the GPU vendors' groups above bit 47.
capabilities.rs:203 ↗
pub enum Capability

Values

Serial{ _ }Serial fallback capability without SIMD. Always available.
Neon{ _ }ARM NEON.
NeonHalf{ _ }ARM NEON FP16.
NeonBfDot{ _ }ARM NEON BF16.
NeonFhm{ _ }ARM NEON FP16 FML.
NeonSdot{ _ }ARM NEON i8 dot.
NeonFp8{ _ }ARM NEON FP8.
Sve{ _ }ARM SVE.
SveHalf{ _ }ARM SVE FP16.
SveSdot{ _ }ARM SVE i8 dot.
SveBfDot{ _ }ARM SVE BF16.
Sve2{ _ }ARM SVE2.
Sme{ _ }ARM SME.
SmeF64{ _ }ARM SME F64.
SmeBi32{ _ }ARM SME BI32I32.
Haswell{ _ }Intel AVX2.
Alder{ _ }Intel AVX2+VNNI.
Sierra{ _ }Intel AVXVNNIINT8.
Skylake{ _ }Intel AVX-512.
IceLake{ _ }Intel AVX-512 VNNI.
Genoa{ _ }AMD AVX-512 BF16.
Turin{ _ }AMD Turin AVX-512 CD.
Sapphire{ _ }Intel AVX-512 FP16.
Diamond{ _ }Intel AVX10.2.
SapphireAmx{ _ }Intel Sapphire AMX.
GraniteAmx{ _ }Intel Granite AMX FP16.
DiamondAmx{ _ }Intel Diamond Rapids AMX.
Rvv{ _ }RISC-V Vector.
RvvBf16{ _ }RISC-V Zvfbfwma.
RvvHalf{ _ }RISC-V Zvfh.
RvvBb{ _ }RISC-V Zvbb.
V128{ _ }WASM SIMD128.
V128Relaxed{ _ }WASM Relaxed SIMD.
PowerVsx{ _ }Power VSX 128-bit SIMD.
LoongsonAsx{ _ }LoongArch LASX 256-bit SIMD.
Cuda{ _ }NVIDIA baseline, running on every CUDA device.
Ampere{ _ }NVIDIA SM 8.0.
Ada{ _ }NVIDIA SM 8.9.
Hopper{ _ }NVIDIA SM 9.x.
Blackwell{ _ }NVIDIA SM 10.x.
BlackwellRtx{ _ }NVIDIA SM 12.x.
BlackwellUltra{ _ }NVIDIA SM 10.3.
Rocm{ _ }AMD baseline, running on every ROCm device.
Cdna3{ _ }AMD gfx942.
Cdna4{ _ }AMD gfx950.
Cdna5{ _ }AMD gfx1250.
Metal{ _ }Apple baseline, running on every Metal device.
Apple9{ _ }Apple GPU family 9.
Apple10{ _ }Apple GPU family 10.

Implements

Clone, Copy, Debug, StructuralPartialEq, PartialEq, Eq, Hash, From<Capability>, BitOr, BitOr<Capability>, Display
StructScopeThe span of a Stream::scope body, which every _on verb takes to queue on that stream.1
capabilities.rs:774 ↗
pub struct Scope<'scope, 'env: 'scope>

A verb borrows what it reads and writes for 'scope, which ends only once the stream is joined.

streamThe stream every verb of this scope queues on, whose capabilities it dispatches with.
capabilities.rs:781 ↗
pub fn stream(&self) -> &Stream
StructStreamA stream of one capability group's device, where verbs and UnifiedAllocator blocks queue work: a vendor's default when null, the CPU's always null.7
capabilities.rs:663 ↗
pub struct Stream

Examples

use numkong::{Capabilities, Stream};

let cpu = Stream::default(Capabilities::cpu_enabled());
assert!(cpu.as_raw().is_null());

// A vendor with no devices fails with `Status::MissingGpu`.
for ordinal in 0..Capabilities::cuda_count_devices()? {
    let stream = Stream::new(Capabilities::cuda_enabled(ordinal)?, ordinal)?;
    stream.synchronize()?;
}

Implements

Debug, Drop
defaultThe default stream of the default device of the group capabilities name.
capabilities.rs:671 ↗
pub fn default(capabilities: Capabilities) -> Self
newA new stream on device ordinal of the vendor capabilities name, a cudaStream_t, a hipStream_t or an id<MTLCommandQueue>, freed on drop.
capabilities.rs:683 ↗
pub fn new(capabilities: Capabilities, ordinal: usize) -> Result<Self, Error>

The CPU gets Stream::default.

Fails with Status::MissingGpu past the last device or where this build lacks the vendor.

from_rawBorrows a stream the caller made and keeps owning, which is never freed here.
capabilities.rs:701 ↗
pub unsafe fn from_raw(capabilities: Capabilities, handle: *mut c_void) -> Self

Safety

handle must be null or a live stream of the vendor capabilities name, a cudaStream_t, a hipStream_t or an id<MTLCommandQueue>, null on the CPU, and outlive the result.

capabilitiesThe capabilities every consumer of this stream dispatches with.
capabilities.rs:710 ↗
pub fn capabilities(&self) -> Capabilities
as_rawThe vendor's handle, null for a default stream.
capabilities.rs:713 ↗
pub fn as_raw(&self) -> *mut c_void
synchronizeWaits for everything queued on this stream, after which whatever its verbs wrote is readable from the host.
capabilities.rs:717 ↗
pub fn synchronize(&self) -> Result<(), Error>

The CPU has nothing to wait for.

scopeRuns body, whose verbs queue on this stream, then joins the stream, even when body fails or unwinds, and reports the first failure, the body's or the join's.
capabilities.rs:739 ↗
pub fn scope<'env, Output>(
    &'env self,
    body: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result<Output, Error>,
) -> Result<Output, Error>

A verb borrows what it reads and writes for the whole scope, so none of it can be touched before the join.

Examples

use numkong::{Capabilities, ShapesPackedMatrix, Stream};

let triangle = [[1.0_f32, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let packs = ShapesPackedMatrix::new(&[&triangle[..]])?;
let stream = Stream::default(Capabilities::cpu_enabled());
let mut distances = [1.0_f64; 1];
stream.scope(|scope| packs.rmsds_on(scope, &packs, &mut distances))?;
assert!(distances[0] < 1e-6);