#include <numkong/numkong.h>#include <stdio.h>int main(void) { nk_f32_t a[] = {1, 2, 3}; nk_f32_t b[] = {4, 5, 6}; nk_f64_t dot = 0; nk_capability_t capabilities = nk_cap_serial_k; nk_cpu_capabilities_enabled(&capabilities); // the capabilities this CPU runs and this build holds nk_cpu_configure_thread(capabilities); nk_status_t status = nk_dot_f32_best(a, b, 3, &dot, capabilities, NULL); // widened f32 → f64 output printf("dot=%f, %s\n", dot, nk_status_name(status)); return status != nk_success_k;}
Every operation has one such dispatch point, named with _best, which runs the best capability in the mask it gets; the root README describes the dispatch model once.
The examples below pass the capabilities of the Quickstart and a null stream, as every CPU call does.
Each capability kernel, like nk_dot_i8_haswell, is also callable directly with the same arguments short of the mask.
To resolve a kernel once and call it many times without naming a capability, library builds provide the punned finder:
The C++ wrappers come in three layers.
Raw-pointer overloads mirror the C ABI one to one, with explicit lengths, strides and task windows for sharded launches.
Overloads over the nk::vector_of and matrix concepts accept any contiguous run, like std::vector, std::array, std::span, a C array or an nk::vector_view, and return their results.
Owning types, like nk::tensor, nk::packed_matrix and nk::packed_attention, allocate through your allocator for repeated workloads.
#include <vector>#include <numkong/numkong.hpp>namespace nk = ashvardanian::numkong;int main() { std::vector<nk::f32_t> a {1, 2, 3}, b {4, 5, 6}; auto [dot, status] = nk::dot<nk::f32_t>(a, b); // nk::f32_t::dot_result_t == nk::f64_t if (nk::failed(status)) return 1; nk::f64_t raw {}; status = nk::dot(a.data(), b.data(), a.size(), &raw, nk_cap_serial_k); // the raw layer, pinned to serial if (nk::failed(status)) return 1; auto [matrix, matrix_status] = nk::matrix<nk::f32_t>::full({2, 3}, nk::f32_t(1)); if (nk::failed(matrix_status)) return 1; auto [packed, packed_status] = nk::packed_matrix<nk::f32_t>::make(matrix.view()); // owning, reused across calls return nk::succeeded(packed_status) && dot == raw ? 0 : 1;}
Standard containers count storage values, so an std::vector<nk::u1x8_t> of 4 bytes holds 32 dimensions, while NumKong’s own vectors and views count dimensions.
Runs must fill whole storage values, so odd bit and nibble lengths pad with zero bits up to the next byte, and a view ending mid-value is refused.
Strided views are refused with nk::status_t::unexpected_dimensions_k, and vector_view::values() turns a contiguous view into an std::span or refuses the same way.
Outputs of elementwise kernels, like nk::scale<nk::f32_t>(a, 2.0f, 1.0f, out), are any mutable run of the same size, and the call returns an nk::status_t.
Multi-part results come back as small structs: nk::reduce_moments returns {sum, sumsq}, nk::reduce_minmax a minmax_result, and nk::shapes_packed::make an owner of many packs.
Every wrapper ends in the two arguments of its dispatch point, defaulted to nk::default_capabilities() and a null stream, and returns an nk::status_t.
That scoped enum mirrors nk_status_t value for value, converts to it with static_cast, and is tested with nk::succeeded and nk::failed rather than as a bool.
Factories, like nk::tensor<T>::zeros, and the allocating overloads return an nk::expected<T> holding the value and its status, which converts to true on success and unpacks with structured bindings.
A failed factory leaves its value empty, while a zero-volume request succeeds with an empty one. nk::default_capabilities() is the nk_cpu_capabilities_enabled mask, and a zero mask runs the C++ reference template instead of any capability’s kernel. nk::device_t names one device by its kind and its runtime’s ordinal, and asks the matching nk_<kind>_* functions for its masks:
nk::device_t const cpu = nk::device_t::cpu();if (auto enabled = cpu.capabilities_enabled()) (void)cpu.configure_thread(enabled.value);auto const [devices, counted] = nk::device_t::count(nk::device_kind_t::cuda_k);for (std::size_t ordinal = 0; ordinal != devices; ++ordinal) if (auto gpu = nk::device_t::make(nk::device_kind_t::cuda_k, ordinal)) if (auto mask = gpu.value.capabilities_enabled()) { /* dispatch over `mask.value` on that GPU */ }
In header-only builds the dispatch points report nk_missing_library_k, so wrappers there take a zero mask or call a capability’s kernel.
The API is intentionally not STL-shaped. vector_view, tensor_view, and matrix_view prioritize signed strides, sub-byte storage, and kernel compatibility over resizable-container ergonomics.
API
Types
status_tWhy a call failed, in the vocabulary the C ABI and every binding share.
Every enumerator takes its value from nk_status_t in types.h, so the C++ mirror and the ABI cannot drift, and the crossing is a cast rather than a table.