NanoGEMM ⚡
NanoGEMM is a minimalist, bare-metal General Matrix Multiplication (GEMM) engine designed for sub-microsecond CPU inference and high-performance computing in Python.
Built with direct AVX2 / FMA (256-bit SIMD) and ARM NEON (128-bit SIMD) assembly-level register tiling and cache blocking, NanoGEMM eliminates the heavy function-call dispatch, thread-pool barriers, and memory-packing overhead of heavyweight BLAS libraries (OpenBLAS, MKL) for small-to-medium tensors.
🚀 Performance Benchmarks
Measured on Intel/AMD x86-64 CPU (AVX2 + FMA) against NumPy 2.2.3 (single-precision float32):
| Matrix Dimension | NumPy 2.2.3 Latency | NanoGEMM Latency | Speedup Factor | NanoGEMM Throughput |
|---|---|---|---|---|
16 x 16 |
3.21 µs |
1.23 µs (C: 0.65 µs) |
🚀 2.83x FASTER | 2.83 GFLOPS |
32 x 32 |
5.75 µs |
2.74 µs (C: 2.18 µs) |
🚀 2.26x FASTER | 15.13 GFLOPS |
64 x 64 |
18.70 µs |
17.76 µs (C: 16.39 µs) |
🚀 1.10x FASTER | 27.08 GFLOPS |
128 x 128 |
114.07 µs |
182.46 µs |
0.60x |
23.42 GFLOPS |
256 x 256 |
426.24 µs |
1501.24 µs |
0.28x |
22.35 GFLOPS |
💡 Why is NanoGEMM faster on small/medium matrices?
Traditional BLAS engines incur 3–10 µs of fixed overhead per invocation due to dynamic runtime dispatch, argument sanitization, thread synchronization, and packing buffers. NanoGEMM utilizes a zero-allocation, direct register-tiled microkernel that executes in sub-microsecond time immediately upon invocation.
⚙️ Benchmark Environment & Test Configuration
| Parameter | Specification |
|---|---|
| CPU Architecture | x86-64 with AVX2 (256-bit SIMD) + FMA3 support |
| Execution Model | Single-Core / Single-Threaded (1 Thread) for NanoGEMM (zero thread-pool overhead) |
| NumPy Baseline | NumPy 2.2+ linked against OpenBLAS (standard runtime) |
| C Compiler | GCC (-O3 -mavx2 -mfma) / MSVC (/O2 /arch:AVX2) |
| Methodology | Median latency across 10,000 iterations per size with cache pre-warming |
| Data Type | IEEE-754 Single-Precision (float32), contiguous C-order layout |
📌 Single-Thread Design Note: NanoGEMM runs strictly on a single CPU core without thread pools, pthreads, or mutex barriers. For small matrices ($16 \times 16$ to $64 \times 64$), thread synchronization in OpenMP costs more cycles than the matrix multiply itself. NumPy/OpenBLAS incurs dispatch latency, whereas NanoGEMM enters CPU registers directly.
⚖️ Architectural Comparison: NanoGEMM vs OpenBLAS vs LIBXSMM
When evaluating CPU matrix multiplication engines, understanding the design trade-offs between compilation models, binary footprint, dispatch latency, and target matrix shapes is critical:
| Feature / Metric | NanoGEMM | LIBXSMM | OpenBLAS / MKL |
|---|---|---|---|
| Core Paradigm | Static AOT Register Tiling ($6\times 16$, $4\times 16$, $2\times 16$) | Runtime JIT Machine-Code Generation | Multi-threaded Dynamic BLAS with Buffer Packing |
| Binary Footprint | ⚡ ~100 KB (ultra-lightweight single binary) | ~15–30 MB | ~30–50 MB |
| Code Generation | Ahead-of-Time (Zero-JIT) | Runtime JIT code emission into executable pages | Ahead-of-Time |
| Security / Hardened OS (W^X) | 🛡️ 100% Compliant (No executable heap/stack required) | Requires writable & executable memory (PROT_EXEC) |
🛡️ 100% Compliant |
| External Dependencies | None (pure C & FASM assembly, zero runtime deps) | C++ runtime, pthreads | OpenMP / pthreads, Fortran runtime |
| Python Invocation Latency | Sub-microsecond (< 1.5 µs) via Buffer Protocol & GIL release | Requires custom wrappers or ctypes bindings | 3–5 µs (NumPy ufunc dispatch, C-API type checking) |
| Optimal Sweet Spot | Small tensors ($16 \times 16$ – $64 \times 64$) in Python / Edge AI | Small-to-medium matrices in pure C/C++ | Large matrices ($512 \times 512$ – $4096 \times 4096+$) |
| Multi-Threading Model | Single-threaded per call (use ThreadPoolExecutor without lock contention) |
Single/Multi-threaded | Multi-threaded OpenMP thread pool |
💡 Key Design Rationale:
- NanoGEMM vs OpenBLAS / MKL on Small Tensors:
Traditional BLAS libraries are optimized for massive matrices ($1000 \times 1000+$). On small matrices ($16 \times 16$ to $64 \times 64$), the fixed overhead of thread synchronization, buffer packing, and NumPy ufunc argument sanitization takes 3–5 µs before arithmetic even begins. NanoGEMM eliminates this glue overhead completely, achieving sub-microsecond latency in Python. - NanoGEMM vs LIBXSMM:
LIBXSMM (Intel Labs) is the gold standard for small-matrix GEMM in pure C/C++ when runtime JIT code emission is permissible. NanoGEMM adopts a different engineering philosophy: Zero-JIT, zero dynamic allocations, zero external dependencies, and a ~100 KB footprint. NanoGEMM deploys seamlessly in hardened environments (where W^X / DEP security policies block runtime JIT emission) and installs instantly via standard Python wheels. - v0.3.3 Boundary Microkernel Acceleration:
In NanoGEMM v0.3.3, dedicated $4 \times 16$ and $2 \times 16$ boundary register tiles (plus $8$-wide column tiles) have been introduced. Standard square power-of-two matrices ($8 \times 8$, $16 \times 16$, $32 \times 32$, $64 \times 64$) now compute 100% inside AVX2+FMA vector registers with zero scalar tail fallback.
🛠 Architectural Design
1. Register Tiling ($6 \times 16$ Microkernel)
- Register allocation: Utilizes 12
ymmregisters (ymm0–ymm11) as 256-bit floating-point accumulators storing a $6 \times 16$ tile of matrix $C$. - Vector broadcast & FMA: Two
ymmregisters load vectors from $B$, while individual scalar elements of $A$ are broadcast acrossymmusing_mm256_set1_psand accumulated via fused multiply-add (_mm256_fmadd_ps). - Zero Spilling: Fits completely inside the 16 available x86-64 YMM registers without stack eviction.
2. Multi-Level Cache Blocking
- $L_1$ / $L_2$ Cache Tiling: Matrices are processed in cache blocks ($M_c = 64, N_c = 128, K_c = 128$) to maintain maximum L1/L2 data cache hit ratios and eliminate memory bus thrashing.
- Vectorized Edge Handling: Arbitrary matrix dimensions (non-multiples of 6 or 16) are processed using boundary SIMD edge loops without padding or buffer allocations.
Matrix A (M x K) Matrix B (K x N)
[ . . . . . . . . ] [ . . . ymm0 . . . ]
[ . . . . . . . . ] [ . . . ymm1 . . . ]
[ a0 a1 a2 a3 . . ] x [ . . . . . . . . ]
[ . . . . . . . . ] [ . . . . . . . . ]
[ . . . . . . . . ]
│ │
└──────────────┬──────────────┘
▼
Matrix C (6 x 16 Tile)
[ ymm0 ymm1 ] -> Row 0
[ ymm2 ymm3 ] -> Row 1
[ ymm4 ymm5 ] -> Row 2
[ ymm6 ymm7 ] -> Row 3
[ ymm8 ymm9 ] -> Row 4
[ ymm10 ymm11 ] -> Row 5
📦 Installation & Quickstart
Installation via PyPI (Recommended)
pip install nanogemm
# or with uv
uv add nanogemm
Python Compatibility: Fully tested and verified across Python 3.9 through 3.15 (including 3.15.0rc2).
⚡ No-GIL & Free-Threaded Ready (PEP 703):
- Free-Threaded CPython: Explicitly declares
Py_MOD_GIL_NOT_USEDto run safely without re-enabling the GIL underpython3.13t,3.14t, and3.15t.- GIL-Releasing Operations: Automatically releases the GIL (
Py_BEGIN_ALLOW_THREADS) during hardware SIMD execution, allowing Python threads (threading.Thread,ThreadPoolExecutor) to run matrix multiplications concurrently across multiple CPU cores without lock contention.
Build from Source
git clone https://github.com/eminsk/nanogemm.git
cd nanogemm
pip install -e .
Python Usage
import nanogemm as ng
import numpy as np
# Verify SIMD hardware acceleration
print("Active ISA:", ng.get_simd_isa())
# Output: Active ISA: AVX2+FMA (256-bit SIMD, 6x16 register tiling)
# Allocate input matrices
A = np.random.randn(32, 64).astype(np.float32)
B = np.random.randn(64, 128).astype(np.float32)
# Direct hardware-accelerated MatMul: C = A @ B
C = ng.matmul(A, B)
# Or with pre-allocated zero-copy output buffer for maximum performance:
out = np.empty((32, 128), dtype=np.float32)
ng.matmul(A, B, out=out)
# Standard BLAS SGEMM interface: C = alpha * (A @ B) + beta * C
res = ng.sgemm(A, B, alpha=2.0, beta=0.5, c=out)
🧪 Testing & Verification
Run the comprehensive correctness test suite comparing NanoGEMM with NumPy reference outputs across random uniforms, normals, non-square dimensions, and prime shapes:
python tests/test_correctness.py
Run the official benchmark against your installed NumPy BLAS:
python benchmarks/bench_vs_numpy.py
⚡ Standalone Flat Assembler (FASM) 32-bit & 64-bit Engines
NanoGEMM includes native standalone assembly implementations written in Flat Assembler (FASM) for both 64-bit and 32-bit architectures in the asm/ directory:
- x86-64 Engine (
asm/nanogemm64.dll,asm/test_nanogemm64.exe):- Microkernel with $4 \times 16$ and $4 \times 8$ register-tiled AVX2+FMA instructions (
vfmadd231ps,vbroadcastss,vmovups). - Complies strictly with the Microsoft x64 ABI (preserving non-volatile registers
RBX,RSI,RDI,R12–R15,XMM6–XMM15). - Peak throughput exceeding 38–40 GFLOPS on a single CPU core.
- Microkernel with $4 \times 16$ and $4 \times 8$ register-tiled AVX2+FMA instructions (
- x86 32-bit Engine (
asm/nanogemm32.dll,asm/test_nanogemm32.exe):- Vectorized $4 \times 4$ SSE2 microkernel (
movups,shufps,mulps,addps) usingcdeclcalling convention. - Compatible with all 32-bit x86 environments and 64-bit Windows via WoW64 with zero external dependencies.
- Delivers 13–15 GFLOPS in pure 32-bit mode.
- Vectorized $4 \times 4$ SSE2 microkernel (
Building & Running FASM Tests
:: Build all 4 binaries and run native executable suites
cd asm
build.bat
:: Run Python verification and NumPy comparison suite
python tests/test_fasm.py
🌐 High-Performance Systems Ecosystem
NanoGEMM is developed by @eminsk as part of an engineering ecosystem focused on low-level hardware performance, assembly programming, and native desktop computing:
- 🎥 screenvideo — Lightweight desktop screen recorder featuring WASAPI loopback audio and a standalone pure x64 Flat Assembler (FASM) native edition.
- 📊 xlsx_vievers — Desktop spreadsheet processor with 80+ formula functions, Chart Wizard, and hardware-accelerated SIMD SSE2 math engine.
- 📈 yfinance-ta-patterns — Candlestick pattern scanner and AI ranking suite powered by TA-Lib and quantitative backtesting.
- 🔍 StackOverflowAPI — Desktop client for Stack Overflow built with CustomTkinter and native FASM x64 search client.
📄 License
MIT License — Copyright (c) 2026 eminsk.
Metadata
Release files for nanogemm 0.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nanogemm-0.3.3.tar.gz | 82.4 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| nanogemm-0.3.3-cp315-cp315-win_amd64.whl | CPython 3.15 | CPython 3.15 | Windows x86-64 | Details |
| nanogemm-0.3.3-cp314-cp314-win_amd64.whl | CPython 3.14 | CPython 3.14 | Windows x86-64 | Details |
| nanogemm-0.3.3-cp313-cp313-win_amd64.whl | CPython 3.13 | CPython 3.13 | Windows x86-64 | Details |
| nanogemm-0.3.3-cp312-cp312-win_amd64.whl | CPython 3.12 | CPython 3.12 | Windows x86-64 | Details |
Total release size: 348.5 kB
Release files / nanogemm-0.3.3.tar.gz
| Download URL | nanogemm-0.3.3.tar.gz |
|---|---|
| Size | 82.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41a7a3511eb313d08a905f1d4bc5e14ff5906dbdbd088a99a5ffca36d0f9e2b0
|
|
BLAKE2b-256 checksum How to use checksums |
59da410bbe0c8a823bb454e9f96dace283a670379000f912c58487036853456a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / nanogemm-0.3.3-cp315-cp315-win_amd64.whl
| Download URL | nanogemm-0.3.3-cp315-cp315-win_amd64.whl |
|---|---|
| Size | 67.3 kB |
| Tags | CPython 3.15 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
3255df5c33a05a0a7b6b8e482dee9e14e7752744333817a6842b66de1c1c0c4d
|
|
BLAKE2b-256 checksum How to use checksums |
84d928003bb2954636003c00c4b4fc567b0832639069648d53d1ced1328a2c27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / nanogemm-0.3.3-cp314-cp314-win_amd64.whl
| Download URL | nanogemm-0.3.3-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 67.3 kB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
904700efcb6b89c9f1eb8b60344fd096ee7238aeeda711274bfd6357c675da8e
|
|
BLAKE2b-256 checksum How to use checksums |
cd49c360995e32e0295019d2c6814b2852f44b20b7acb6c273254b354163cc0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / nanogemm-0.3.3-cp313-cp313-win_amd64.whl
| Download URL | nanogemm-0.3.3-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 65.8 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
f674f1dfaa6da015ab4308bf21fbdc2de2bf83f467539b31d705712d5506c9cd
|
|
BLAKE2b-256 checksum How to use checksums |
ba79eb9152db4ef659dc639cdc09d1cf0eff68f8a7a9a3f938e36ff73dcac9c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / nanogemm-0.3.3-cp312-cp312-win_amd64.whl
| Download URL | nanogemm-0.3.3-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 65.8 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
6d9b945ad16b1bd0a1e5524cb55f9d6e75f9e945883dd63b8474a04d66db550c
|
|
BLAKE2b-256 checksum How to use checksums |
63542b429d72f044c38edc69c50fd5b66aa3eb0fe9f5497c8dbd833473025274
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|