Expand description
§gemmkit
A GEMM (general matrix multiply) engine with no ndarray dependency. It computes
C <- alpha*A*B + beta*C over data-type-agnostic &[T] and stride views, or raw
pointers. It picks the fastest instruction set the running CPU supports
§Quick start
use gemmkit::{gemm, MatRef, MatMut, Parallelism};
// 2x3 * 3x2 = 2x2, row-major
let a = [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let b = [7.0_f32, 8.0, 9.0, 10.0, 11.0, 12.0];
let mut c = [0.0_f32; 4];
gemm(
1.0,
MatRef::from_row_major(&a, 2, 3),
MatRef::from_row_major(&b, 3, 2),
0.0,
MatMut::from_row_major(&mut c, 2, 2),
Parallelism::Serial,
);
assert_eq!(c, [58.0, 64.0, 139.0, 154.0]);§Architecture in brief
Every axis of variation lives behind a trait. The instruction set is
simd::SimdOps, the element type is scalar::Scalar, and the operation
family is kernel::KernelFamily. One generic 5-loop driver and one generic
microkernel cover every (type, ISA, tile) combination with no macros and no
transmute. See ARCHITECTURE.md for the full tour
§Features
std(default): runtime cache and CPU-feature detection, theGEMMKIT_REQUIRE_ISAenv var, tuning knobs, and the thread-local workspace pool. Withstdoff, the crate is#![no_std]and needs onlycoreandallocparallel(default, impliesstd): rayon multithreading. With it off, the crate still compiles and runs, single-threadedwasm_threads(impliesparallel): a stable-toolchain rayon thread pool for a threaded wasm target, sized from a tuning knob because wasm cannot report its own core countcomplex: complex GEMM overc32/c64with optional conjugation (gemm_cplx). Pulls innum-complexhalf:f16/bf16mixed-precision GEMM, accumulating inf32. Pulls inhalfint8:i8 -> i32integer GEMM (gemm_i8), with no extra dependencyepilogue: fused epilogues for bias and activation (gemm_fused,gemm_batched_fused*, the prepackedgemm_packed_a_fused*andgemm_packed_b_fused*, and, withcomplex,gemm_cplx_fused*). Also a user-defined per-element closure (gemm_map,f32/f64only), and, withint8, requantizedi8/u8output (gemm_i8_requant*). No extra dependency
complex, half, and int8 are off by default, so a plain f32/f64 build pays
for none of their codegen or dependencies. epilogue is off by default too, so a
plain-GEMM build pays for none of its codegen
Re-exports§
pub use kernel::epilogue::BiasDim;epiloguepub use scalar::ComplexFloat;complexpub use scalar::NarrowFloat;halfpub use scalar::Float;pub use scalar::Scalar;pub use cache::CacheTopology;pub use cache::Machine;pub use cache::topology;pub use half::bf16;halfpub use half::f16;halfpub use num_complex::Complex;complex
Modules§
- cache
- Cache topology and analytical blocking (layer L3)
- driver
- The generic GEMM driver (layer L5): one 5-loop nest, fully generic over the
KernelFamilyand the ISA token. It never names a concrete element type, a concrete ISA, or a macro, so adding a family or an ISA never touches this file. This is the open/closed property the architecture promises. A test proves it by declaring a 2nd family and driving it through this samerun - kernel
- Kernel families (layer L4): the seam between the driver and a concrete kind of GEMM
- scalar
- The element-type seam (layer L0): what a value must supply to be a GEMM operand
- simd
- SIMD abstraction layer (L0): the per-ISA register vocabulary every microkernel builds on
- tuning
- Unified tuning surface (cross-cutting)
Structs§
- Batch
Problem - 1 element of a checked pointer-array batched GEMM (
gemm_batched_slice):C <- alpha*A*B + beta*Cover safe views - Gemm
Problem - One problem for the pointer-array batched API (
crate::gemm_batched_ptr_unchecked): the sameC <- alpha*A*B + beta*CasTask, as raw pointers andisizeelement strides. Each batch entry can have its own shape and live anywhere in memory. The stridedcrate::gemm_batchedinstead shares one shape and steps every operand by a fixed batch stride - MatMut
- A mutable strided matrix view over a slice
- MatRef
- An immutable strided matrix view over a slice
- Packed
Lhs - An
Amatrix packed once into gemmkit’s internal micropanel-major layout, for reuse across many products that share the sameA. This fits a fixed weight matrix streamed against differently shapedBoperands.prepack_lhsbuilds it.gemm_packed_aand its_with,_unchecked, and_fusedsiblings consume it and skip the per-call LHS pack - Packed
Rhs - A
Bmatrix packed once into gemmkit’s internal micropanel-major layout, for reuse across many products that share the sameB. This fits a fixed weight matrix streamed against many activation matrices.prepack_rhsbuilds it.gemm_packed_band its_with,_unchecked, and_fusedsiblings consume it and skip the per-call RHS pack - Requantize
epilogueandint8 - The quantization parameters for the requantizing entries:
RequantScale, an integerzero_point, and an optional per-rowi32bias (lengthm, the standard qlinear layer bias). The output isC[i,j] = clamp(zero_point + round_ne(scale*(sum_k A*B + bias[i])), LO, HI)with round-half-to-even.scaleis the per-tensor value or the per-rowscale_i.[LO, HI]is set by the entry:[-128, 127]forgemm_i8_requant,[0, 255]forgemm_i8_requant_u8 - Workspace
- A growable, 64-byte-aligned scratch buffer for packing A and B
Enums§
- Activation
epilogue - The activation a
gemm_fusedcall applies last, after the bias add - Bias
epilogue - 1 bias value broadcast across a whole output row or column, combined into a
gemm_fusedcall’s epilogue after thealpha*A*B + beta*Cproduct and before the activation - Parallelism
- Threading strategy for a GEMM call
- Requant
Scale epilogueandint8 - The output scale for the requantizing entries: either 1 value applied to the whole tensor,
or 1 value per output row/channel (the per-channel quantized-inference convention). Every
scale must be finite and
> 0
Traits§
- Complex
Scalar complex - Complex element types gemmkit can dispatch:
Complex<f32>andComplex<f64> - Fused
Scalar epilogue - The sealed element-type bound for the fused-epilogue public API
- Gemm
Scalar - The dispatch layer runs
f32andf64directly, as a homogeneous float. Underhalf, it also runsf16andbf16as mixed precision, withAcc = f32 - MapScalar
epilogue - The sealed element-type bound for the user-defined map-epilogue public API
(
crate::gemm_map): the real floatsf32/f64only
Functions§
- gemm
C <- alpha*A*B + beta*Cover safe slice views, using the thread-local workspace pool- gemm_
batched - Strided-batched GEMM:
C_b <- alpha*A_b*B_b + beta*C_bforb in 0..batch, in 1 call, parallelized across the batch rather than within each element. Every element shares the single-element shape and strides ofa/b/c. Elementbis based ata.data + b*a_batch_stride, and likewise forb/c. A*_batch_strideof0broadcasts 1 operand across the whole batch, valid for the read-onlyA/Bbut never forC. Uses the thread-local workspace pool - gemm_
batched_ fused epilogue - Strided-batched GEMM with a fused epilogue shared by every element:
C_b <- act(alpha*A_b*B_b + beta*C_b + bias)forb in 0..batch, in 1 call, parallelized across the batch. 1 bias vector and 1 activation apply to every element, the batched-linear-layer case of 1 layer applied to a batch of inputs. Shape, stride, and broadcast conventions matchgemm_batched. Uses the thread-local workspace pool. Whenbias == None && act == None, this takes the plaingemm_batchedpath - gemm_
batched_ ⚠fused_ unchecked epilogue - The raw strided-batched fused engine:
C_e <- act(alpha*A_e*B_e + beta*C_e + bias)fore in 0..batch, over pointers andisizestrides, with no bounds, alias, or shape checks. This is the raw-parts form ofgemm_batched_fused, combininggemm_batched_unchecked’s per-element shape with the shared bias/activation ofgemm_fused_unchecked. Elementeis based ata + e*a_batch_stride/b + e*b_batch_stride/c + e*c_batch_stride, all sharing the single-element shape(m, k, n)and element strides. The 1bias(a(ptr, dim)pair, read only whenhas_bias) and 1actapply to every element. Uses the thread-local workspace pool - gemm_
batched_ ⚠fused_ unchecked_ with epilogue - As
gemm_batched_fused_uncheckedbut with a caller-ownedWorkspace - gemm_
batched_ fused_ with epilogue - Like
gemm_batched_fusedbut reuses a caller-ownedWorkspace. This uses the same split asgemm_batched_with: the serial and few-but-large schedules pack throughws. The batch-parallel schedule packs through each worker’s own thread-local pool instead - gemm_
batched_ ⚠ptr_ unchecked - Runs a pointer-array batched GEMM: every element in
problemsis an independent product with its own shape and pointers (GemmProblem). This parallelizes across the batch, with whole GEMMs assigned to workers, each run serially and cache-hot. The raw counterpart ofgemm_batched_slice, for callers (FFI, adapters) that validate their own inputs and may use arbitrary pointers or negative strides. Deterministic across thread counts, since each element runs wholly on 1 worker, and takes theproblemsslice as-is with no per-call allocation - gemm_
batched_ slice - Runs a checked pointer-array batched GEMM:
problems[i].c <- alpha*A*B + beta*Cfor each element, each an independent product over safe views, parallelized across the batch. This is the safe counterpart ofgemm_batched_ptr_unchecked. Because everycis a distinctMatMut, the outputs are pairwise disjoint and cannot alias the inputs by construction. The borrow checker already forbids 2 overlapping&mutborrows, so validation only covers per-element shape agreement, in-bounds strides, and self-aliasing. Deterministic across thread counts - gemm_
batched_ ⚠unchecked - The raw strided-batched engine:
gemm_batchedover pointers andisizestrides, with no bounds, alias, or shape checks. Elementeis based ata + e*a_batch_stride/b + e*b_batch_stride/c + e*c_batch_stride, all sharing the single-element shape(m, k, n)and element strides. Adapter crates (e.g. an ndarrayArray3batched on axis 0) and FFI callers that supply their own pointers or arbitrary strides use this path. Uses the thread-local workspace pool - gemm_
batched_ ⚠unchecked_ with - As
gemm_batched_uncheckedbut with a caller-ownedWorkspace - gemm_
batched_ with - Like
gemm_batchedbut reuses a caller-ownedWorkspaceacross calls. The serial and few-but-large schedules pack throughws. The batch-parallel schedule instead has each worker pack through its own thread-local pool, since 1Workspacecannot back concurrent packing from several threads - gemm_
cplx complex - Complex GEMM with optional conjugation:
C <- alpha*op(A)*op(B) + beta*C, whereop(A) = conj(A)whenconj_a(independently,op(B) = conj(B)whenconj_b).TisComplex<f32>orComplex<f64>(re-exported ascrate::c32/crate::c64). Uses the thread-local workspace pool - gemm_
cplx_ fused complexandepilogue - Complex GEMM with an optional fused per-row / per-col bias:
C <- alpha*op(A)*op(B) + beta*C + biasin 1 pass, whereopconjugates an operand exactly as ingemm_cplx.TisComplex<f32>orComplex<f64>. Uses the thread-local workspace pool - gemm_
cplx_ ⚠fused_ unchecked complexandepilogue - The raw complex fused engine:
C <- alpha*op(A)*op(B) + beta*C + biasover pointers andisizestrides, with no bounds, alias, or shape checks: the raw-parts form ofgemm_cplx_fused.opconjugates an operand when itsconj_*flag is set.biasis a(ptr, dim)pair, read only whenhas_bias, and added verbatim, never conjugated. There is no activation parameter, since it has no definition on complex numbers. Uses the thread-local workspace pool - gemm_
cplx_ ⚠fused_ unchecked_ with complexandepilogue - As
gemm_cplx_fused_uncheckedbut with a caller-ownedWorkspace - gemm_
cplx_ fused_ with complexandepilogue - Like
gemm_cplx_fusedbut reuses a caller-ownedWorkspace - gemm_
cplx_ ⚠unchecked complex - The raw complex engine:
C <- alpha*op(A)*op(B) + beta*Cover pointers andisizestrides, with no bounds, alias, or shape checks: the complex counterpart ofgemm_unchecked, whereopconjugates an operand when itsconj_*flag is set. Adapter crates (e.g. ndarray) use this path to express transposed or negative strides that the checked API rejects. Uses the thread-local workspace pool - gemm_
cplx_ ⚠unchecked_ with complex - As
gemm_cplx_uncheckedbut with a caller-ownedWorkspace - gemm_
cplx_ with complex - Like
gemm_cplxbut reuses a caller-ownedWorkspace - gemm_
fused epilogue C <- act(alpha*A*B + beta*C + bias)in 1 pass over safe slice views, using the thread-local workspace pool.bias == None && act == Nonedelegates to plaingemm- gemm_
fused_ ⚠unchecked epilogue - The raw fused engine:
C <- act(alpha*A*B + beta*C + bias)over pointers andisizestrides, with no bounds, alias, or shape checks.biasis a(ptr, dim)pair, read only whenhas_bias. Uses the thread-local workspace pool - gemm_
fused_ ⚠unchecked_ with epilogue - As
gemm_fused_uncheckedbut with a caller-ownedWorkspace - gemm_
fused_ with epilogue - Like
gemm_fusedbut reuses a caller-ownedWorkspace. Accepts the samef32/f64and, underhalf,f16/bf16types, with the same pre-narrowf32epilogue precision described atgemm_fused - gemm_i8
int8 - Integer GEMM:
C <- alpha*A*B + beta*Cwithi8inputs accumulated into ani32output (alpha,beta,Carei32). Wraps on overflow, the standard integer-GEMM convention. Uses the thread-local workspace pool - gemm_
i8_ packed_ b int8 C(i32) <- alpha*A(i8)*(prepacked B) + beta*C, consuming aPackedRhs<i8>(Bprepacked once) instead ofBitself, using the thread-local workspace pool. This is the integer (i8 -> i32) twin ofgemm_packed_b. It skips the RHS pack that, for the VNNI kernel, would otherwise run on every call- gemm_
i8_ ⚠packed_ b_ unchecked int8 - As
gemm_i8_packed_bbut over rawA/Cpointers and strides, with no bounds or alias checks. This is the heterogeneous (i8 -> i32) counterpart ofgemm_packed_b_unchecked. The sharedkand outputncome frompacked, andmisA’s row count, the same asC’s. Uses the thread-local workspace pool - gemm_
i8_ ⚠packed_ b_ unchecked_ with int8 - As
gemm_i8_packed_b_uncheckedbut with a caller-ownedWorkspace - gemm_
i8_ packed_ b_ with int8 - Like
gemm_i8_packed_bbut reuses a caller-ownedWorkspace - gemm_
i8_ requant epilogueandint8 - Requantizing integer GEMM:
i8inputs multiply into ani32accumulator, then requantize to ani8output in 1 pass. This skips the fullm x ni32materialization that a separategemm_i8call followed by a requantize pass would need. Noalpha(it folds intoscale) and nobeta(accumulating into an already-quantizedCis not well-defined). Uses the thread-local workspace pool - gemm_
i8_ requant_ u8 epilogueandint8 - Requantizing integer GEMM with an unsigned
u8output, the ONNX QLinearMatMul activation convention.i8inputs multiply into ani32accumulator, then requantize in 1 pass toC[i,j] = clamp(zero_point + round_ne(scale*(sum_k A*B + bias[i])), 0, 255)with round-half-to-even.scaleis the per-tensor value or the per-rowscale_i. This is theu8-output twin ofgemm_i8_requant, differing only in the output domain ([0, 255]instead of[-128, 127]) and the acceptedzero_pointrange. Noalpha(it folds intoscale) and nobeta(accumulating into an already-quantizedCis not well-defined). Uses the thread-local workspace pool - gemm_
i8_ ⚠requant_ u8_ unchecked epilogueandint8 C(u8) <- clamp(zp + round_ne(scale*(A*B + bias)), 0, 255)over raw pointers andisizeelement strides, with no bounds, alias, or shape checks: the unsigned twin ofgemm_i8_requant_unchecked.biasis a per-rowi32pointer, read only whenhas_bias. The scale is the scalarscaleunlesshas_row_scalesis set, in which caserow_scalessupplies 1f32per output row (lengthm) instead. Uses the thread-local workspace pool- gemm_
i8_ ⚠requant_ u8_ unchecked_ with epilogueandint8 - Like
gemm_i8_requant_u8_uncheckedbut reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
i8_ requant_ u8_ with epilogueandint8 - Like
gemm_i8_requant_u8but reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
i8_ ⚠requant_ unchecked epilogueandint8 C(i8) <- clamp(zp + round_ne(scale*(A*B + bias)), -128, 127)over raw pointers andisizeelement strides, with no bounds, alias, or shape checks.biasis a per-rowi32pointer, read only whenhas_bias. The scale is the scalarscaleunlesshas_row_scalesis set, in which caserow_scalessupplies 1f32per output row (lengthm) instead. Uses the thread-local workspace pool- gemm_
i8_ ⚠requant_ unchecked_ with epilogueandint8 - Like
gemm_i8_requant_uncheckedbut reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
i8_ requant_ with epilogueandint8 - Like
gemm_i8_requantbut reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
i8_ ⚠unchecked int8 C(i32) <- alpha*A(i8)*B(i8) + beta*Cover raw pointers andisizeelement strides, with no bounds, alias, or shape checks. This is thei8 -> i32escape hatch for negative strides or raw-pointer callers.gemm_uncheckedis typed for the homogeneous surface and cannot express a differing output type. Uses the thread-local workspace pool- gemm_
i8_ ⚠unchecked_ with int8 - Like
gemm_i8_uncheckedbut reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
i8_ with int8 - Like
gemm_i8but reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
map epilogue C[r, c] <- f(alpha*A*B + beta*C, r, c)in 1 fused pass. This is a plain GEMM with a caller closure applied to each output element at its final value. It runs over safe slice views, using the thread-local workspace pool.(r, c)is the user-frame coordinate ofC: rowr, columnc.ffires exactly once per element, at the point the plain kernel would store it- gemm_
map_ ⚠unchecked epilogue C[r, c] <- f(alpha*A*B + beta*C, r, c)over raw pointers andisizeelement strides, with no bounds, alias, or shape checks.(r, c)is the user-frame coordinate ofC. Uses the thread-local workspace pool- gemm_
map_ ⚠unchecked_ with epilogue - Like
gemm_map_uncheckedbut reuses a caller-ownedWorkspaceinstead of the thread-local pool - gemm_
map_ with epilogue - Like
gemm_mapbut reuses a caller-ownedWorkspaceinstead of the thread-local pool: zero heap allocation once the workspace has grown to fit the 1st sufficiently large call - gemm_
packed_ a C <- alpha*A*B + beta*C, consuming aPackedLhs(Aprepacked once) instead ofAitself, using the thread-local workspace pool. This skips the per-call LHS pack thatgemmwould run- gemm_
packed_ a_ fused epilogue C <- act(alpha*(prepacked A)*B + beta*C + bias)in one pass. This is a fused epilogue over a reusedPackedLhs, using the thread-local workspace pool. It is the fused twin ofgemm_packed_a. The bias is folded in with 1 IEEE add right after the finalbeta-scaled store. The activation applies next, fused into the same store the packed kernel already runs.bias == None && act == Nonereproducesgemm_packed_abit-for-bit- gemm_
packed_ ⚠a_ fused_ unchecked epilogue - As
gemm_packed_a_fusedbut over rawB/Cpointers and strides, with no bounds or alias checks.biasis a(ptr, dim)pair, enabled byhas_biasand ignored whenhas_bias == false, in the user frame, where aPerRowbias indexesA.rows, the same asC.rows.actapplies last. Uses the thread-local workspace pool - gemm_
packed_ ⚠a_ fused_ unchecked_ with epilogue - As
gemm_packed_a_fused_uncheckedbut with a caller-ownedWorkspace - gemm_
packed_ a_ fused_ with epilogue - Like
gemm_packed_a_fusedbut reuses a caller-ownedWorkspace - gemm_
packed_ ⚠a_ unchecked - As
gemm_packed_abut over rawB/Cpointers and strides, with no bounds or alias checks. The sharedkand output-row countmcome frompacked, andnisB’s column count, the same asC’s. Uses the thread-local workspace pool - gemm_
packed_ ⚠a_ unchecked_ with - As
gemm_packed_a_uncheckedbut with a caller-ownedWorkspace - gemm_
packed_ a_ with - Like
gemm_packed_abut reuses a caller-ownedWorkspace - gemm_
packed_ b C <- alpha*A*B + beta*C, consuming aPackedRhs(Bprepacked once) instead ofBitself, using the thread-local workspace pool. This skips the per-call RHS pack thatgemmwould run- gemm_
packed_ b_ fused epilogue C <- act(alpha*A*(prepacked B) + beta*C + bias)in one pass. This is a fused epilogue over a reusedPackedRhs, using the thread-local workspace pool. It is the fused twin ofgemm_packed_b. The bias is folded in with 1 IEEE add right after the finalbeta-scaled store. The activation applies next, fused into the same store the packed kernel already runs.bias == None && act == Nonereproducesgemm_packed_bbit-for-bit- gemm_
packed_ ⚠b_ fused_ unchecked epilogue - As
gemm_packed_b_fusedbut over rawA/Cpointers and strides, with no bounds or alias checks.biasis a(ptr, dim)pair, enabled byhas_biasand ignored whenhas_bias == false, in the user frame, because this path never swaps orientation.actapplies last. Uses the thread-local workspace pool - gemm_
packed_ ⚠b_ fused_ unchecked_ with epilogue - As
gemm_packed_b_fused_uncheckedbut with a caller-ownedWorkspace - gemm_
packed_ b_ fused_ with epilogue - Like
gemm_packed_b_fusedbut reuses a caller-ownedWorkspace - gemm_
packed_ ⚠b_ unchecked - As
gemm_packed_bbut over rawA/Cpointers and strides, with no bounds or alias checks. The sharedkand outputncome frompacked, andmisA’s row count, the same asC’s. Uses the thread-local workspace pool - gemm_
packed_ ⚠b_ unchecked_ with - As
gemm_packed_b_uncheckedbut with a caller-ownedWorkspace - gemm_
packed_ b_ with - Like
gemm_packed_bbut reuses a caller-ownedWorkspace - gemm_
unchecked ⚠ - The raw engine:
C <- alpha*A*B + beta*Cover pointers andisizestrides, with no bounds, alias, or shape checks. Uses the thread-local workspace pool - gemm_
unchecked_ ⚠with - Like
gemm_uncheckedbut reuses a caller-ownedWorkspace - gemm_
with - Like
gemmbut reuses a caller-ownedWorkspace: zero heap allocation once the workspace has grown to fit the 1st sufficiently large call - prepack_
lhs - Pack an
m x kAview into aPackedLhsfor reuse across manygemm_packed_acalls. The pack runs once, single-threaded, right here, so every later call skips it - prepack_
lhs_ ⚠unchecked - As
prepack_lhsbut over a rawm x kApointer and strides, with no bounds check. Use this raw form for an adapter or FFI caller that validates its own inputs - prepack_
rhs - Pack a
k x nBview into aPackedRhsfor reuse across manygemm_packed_bcalls. The pack runs once, single-threaded, right here, so every later call skips it - prepack_
rhs_ i8 int8 - Pack a
k x ni8RHS into aPackedRhs<i8>for reuse across manygemm_i8_packed_bcalls. This fits the quantized-inference pattern of constanti8weights against a stream ofi8activation batches. The pack runs once, single-threaded, right here, so later calls skip it - prepack_
rhs_ ⚠i8_ unchecked int8 - As
prepack_rhs_i8but over a rawk x nBpointer and strides, with no bounds check. Use this raw form for an adapter or FFI caller that validates its own inputs - prepack_
rhs_ ⚠unchecked - As
prepack_rhsbut over a rawk x nBpointer and strides, with no bounds check. Use this raw form for an adapter or FFI caller that validates its own inputs