MCPcopy Index your code
hub / github.com/ashvardanian/NumKong

github.com/ashvardanian/NumKong @v7.7.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v7.7.0 ↗ · + Follow
8,525 symbols 21,556 edges 453 files 1,939 documented · 23%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

NumKong: Mixed Precision for All

Portable mixed-precision math, linear-algebra, & retrieval library with 2'000+ SIMD kernels for x86, Arm, RISC-V, LoongArch, Power, & WebAssembly, leveraging rare algebraic transforms with both 1D & 2D registers like AMX & SME, covering 15+ numeric types from 4-bit integers & 6-bit floats to 128-bit complex numbers, validated against 118-bit extended-precision baselines with saturation, casting, & rounding edge-case coverage, in a 5-100x smaller binary than other BLAS-like alternatives, co-designed with Tensor abstractions in C++, Python, Rust, JavaScript, GoLang, & Swift.

NumKong banner

Latency, Throughput, & Numerical Stability

Most libraries return dot products in the same type as the input — Float16 × Float16 → Float16, Int8 × Int8 → Int8. This leads to quiet overflow: a 2048-dimensional i8 dot product can reach ±10 million, but i8 maxes out at 127. NumKong promotes to wider accumulators — Float16 → Float32, BFloat16 → Float32, Int8 → Int32, Float32 → Float64 — so results stay in range.

Input NumPy + OpenBLAS PyTorch + MKL JAX NumKong
░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
f64 2.0 gso/s, 1e-15 err 0.6 gso/s, 1e-15 err 0.4 gso/s, 1e-14 err 5.8 gso/s, 1e-16 err
f32 1.5 gso/s, 2e-6 err 0.6 gso/s, 2e-6 err 0.4 gso/s, 5e-6 err 7.1 gso/s, 2e-7 err
bf16 0.5 gso/s, 1.9% err 0.5 gso/s, 1.9% err 9.7 gso/s, 1.8% err
f16 0.2 gso/s, 0.25% err 0.5 gso/s, 0.25% err 0.4 gso/s, 0.25% err 11.5 gso/s, 0.24% err
e5m2 0.7 gso/s, 4.6% err 0.5 gso/s, 4.6% err 7.1 gso/s, 0% err
i8 1.1 gso/s, overflow 0.5 gso/s, overflow 0.5 gso/s, overflow 14.8 gso/s, 0% err

Single 2048-d dot product on Intel Sapphire Rapids, single-threaded. Each cell shows gso/s, mean relative error vs higher-precision reference. gso/s = Giga Scalar Operations per Second — a more suitable name than GFLOP/s when counting both integer and floating-point work. NumPy 2.4, PyTorch 2.10, JAX 0.9.

A fair objection: PyTorch and JAX are designed for throughput, not single-call latency. They lower execution graphs through XLA or vendored BLAS libraries like Intel MKL and Nvidia cuBLAS. So here's the same comparison on a throughput-oriented workload — matrix multiplication:

Input NumPy + OpenBLAS PyTorch + MKL JAX NumKong
░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░
f64 65.5 gso/s, 1e-15 err 68.2 gso/s, 1e-15 err ~14.3 gso/s, 1e-15 err 8.6 gso/s, 1e-16 err
f32 140 gso/s, 9e-7 err 145 gso/s, 1e-6 err ~60.5 gso/s, 1e-6 err 37.7 gso/s, 4e-7 err
bf16 851 gso/s, 1.8% err ~25.8 gso/s, 3.4% err 458 gso/s, 3.6% err
f16 0.3 gso/s, 0.25% err 140 gso/s, 0.37% err ~26.1 gso/s, 0.35% err 103 gso/s, 0.26% err
e5m2 0.4 gso/s, 4.6% err ~26.4 gso/s, 4.6% err 398 gso/s, 0% err
i8 0.4 gso/s, overflow 50.0 gso/s, overflow ~0.0 gso/s, overflow 1279 gso/s, 0% err

Matrix multiplication (2048 × 2048) × (2048 × 2048) on Intel Sapphire Rapids, single-threaded. gso/s = Giga Scalar Operations per Second, same format. NumPy 2.4, PyTorch 2.10, JAX 0.9, same versions.

For f64, compensated "Dot2" summation reduces error by 10–50× compared to naive Float64 accumulation, depending on vector length. For f32, widening to Float64 gives 5–10× lower error. The library ships as a relatively small binary:

Package Size Parallelism & Memory Available For
PyTorch + MKL 705 MB Vector & Tile SIMD, OpenMP Threads, Hidden Allocs Python, C++, Java
JAX + jaxlib 357 MB Vector SIMD, XLA Threads, Hidden Allocs Python
NumPy + OpenBLAS 30 MB Vector SIMD, Built-in Threads, Hidden Allocs Python
mathjs 9 MB No SIMD, No Threads, Many Allocs JS
NumKong 5 MB Vector & Tile SIMD, Your Threads, Your Allocs 7 languages

Every kernel is validated against 118-bit extended-precision baselines with per-type ULP budgets across log-normal, uniform, and Cauchy input distributions. Tests check triangle inequality, Cauchy-Schwarz bounds, NaN propagation, overflow detection, and probability-simplex constraints for each ISA variant. Results are cross-validated against OpenBLAS, Intel MKL, and Apple Accelerate. A broader throughput comparison is maintained in NumWars.

Quick Start

Language Install Compatible with Guide
C / C++ CMake, headers, & prebuilt Linux, macOS, Windows, Android include/README.md
Python pip install Linux, macOS, Windows python/README.md
Rust cargo add Linux, macOS, Windows rust/README.md
JS npm install & import Node.js, Bun, Deno & browsers javascript/README.md
Swift Swift Package Manager macOS, iOS, tvOS, watchOS swift/README.md
Go go get Linux, macOS, Windows via cGo golang/README.md

What's Inside

NumKong covers 17 numeric types — from 6-bit floats to 64-bit complex numbers — across dozens of operations and 30+ SIMD backends, with hardware-aware defaults: Arm prioritizes f16, x86 prioritizes bf16.

Language Bindings

Operation C 99 & C++ 23 Python Rust JavaScript Swift GoLang
Vector Ops
Dot Product
Spatial Metric
Set Similarity
Geospatial ·
Mesh Alignment · · ·
Sparse Products · · ·
Probability Divergences ·
Curved Spaces · · ·
Many-to-Many Vector Ops
"Dots" Products
"Spatials" Metrics
"Sets" Similarities ·
MaxSim Scoring ·
Scalar Ops
Cast · ·
Reduce · · ·
Each · · ·
Trigonometry · · ·

Design Decisions

  • Avoid loop unrolling and scalar tails.
  • Don't manage threads and be compatible with any parallelism models.
  • Don't manage memory and be compatible with arbitrary allocators & alignment.
  • Don't constrain ourselves to traditional BLAS-like Matrix Multiplication APIs.
  • Don't throw exceptions and pass values by pointers.
  • Prefer saturated arithmetic and avoid overflows, where needed.
  • Cover most modern CPUs with flexible dispatch and wait for them to converge with GPUs.

The rest of this document unpacks the functionality and the logic behind the design decisions.

Auto-Vectorization & Loop Unrolling

Most "optimized SIMD code" is a 2–4x unrolled data-parallel for-loop over f32 arrays with a serial scalar tail for the last few elements:

float boring_dot_product_f32(float const *a, float const *b, size_t n) {
    __m256 sum0 = _mm256_setzero_ps(), sum1 = _mm256_setzero_ps();
    size_t i = 0;
    for (; i + 16 <= n; i += 16) {
        sum0 = _mm256_fmadd_ps(_mm256_loadu_ps(a + i), _mm256_loadu_ps(b + i), sum0);
        sum1 = _mm256_fmadd_ps(_mm256_loadu_ps(a + i + 8), _mm256_loadu_ps(b + i + 8), sum1);
    }
    float result = _mm256_reduce_add_ps(_mm256_add_ps(sum0, sum1));
    for (; i < n; i++) result += a[i] * b[i]; // serial tail
    return result;
}

This kind of unrolling has been a common request for NumKong, but the library avoids it by design.

Modern CPUs already "unroll" in hardware. Out-of-order engines with reorder buffers of 320–630 entries (Zen 4: 320, Golden Cove: 512, Apple Firestorm: ~630) can keep a dozen of loop iterations in-flight simultaneously. The physical register file is much larger than the ISA-visible architectural registers — Skylake has ~180 physical integer registers behind 16 architectural GPRs, and ~168 physical vector registers behind 32 architectural ZMMs. The register renaming unit maps the same zmm0 in iteration N and iteration N+1 to different physical registers, extracting cross-iteration parallelism automatically — exactly the benefit that source-level unrolling was historically supposed to provide.

Unrolling works against NumKong's goals. Every unrolled copy is a distinct instruction in the binary. With 1,500+ kernel endpoints across 30+ backends, even 2x unrolling would inflate the .text section by megabytes — directly impacting install size for Python wheels, NPM packages, and Rust crates. Larger loop bodies also increase instruction-cache and micro-op-cache pressure; Agner Fog also recommends:

"avoid loop unrolling where possible in order to economize the use of the micro-op cache".

A loop that spills out of the uop cache falls back to the slower legacy decoder, making the "optimized" version slower than the compact original. For a header-only library, unrolling also compounds compilation time: register allocation is NP-hard (reducible to graph coloring), and unrolling multiplies the number of simultaneously live ranges the allocator must consider, increasing compile time super-linearly across every translation unit that includes the headers.

Serial tails are a correctness hazard. The leftover elements after the last full SIMD chunk run through a scalar loop that silently drops FMA fusion, compensated accumulation, and saturating arithmetic — producing results with different numerical properties than the SIMD body. NumKong often uses masked loads instead (_mm512_maskz_loadu_ps on AVX-512, predicated svld1_f32 on SVE), processing every element through the same arithmetic path regardless of alignment. It's not exactly orthogonal to loop-unrolling, but makes a different kernel layout more comp

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Function 5,781
Method 2,314
Class 331
Interface 79
Enum 10
Route 7
Struct 3

Languages

C++73%
Rust10%
C7%
Python4%
TypeScript3%
Go2%

Modules by API surface

include/numkong/types.hpp1,573 symbols
rust/tensor.rs234 symbols
include/numkong/tensor.hpp190 symbols
rust/matrix.rs128 symbols
include/numkong/reduce/skylake.h125 symbols
include/numkong/reduce/haswell.h123 symbols
include/numkong/cast/serial.h115 symbols
python/tensor.c106 symbols
rust/vector.rs104 symbols
rust/types.rs104 symbols
include/numkong/reduce/rvv.h104 symbols
include/numkong/vector.hpp97 symbols

For agents

$ claude mcp add NumKong \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page