tune_gemm design
Design goal
tune_gemm is the reference domain for tune: a problem where blocking
parameters matter, correctness can be checked exactly, and every candidate can
be serialized. It shows the full tuning discipline (validate before timing,
account for workspace, test tails, record the configuration and the
environment) on matrix multiplication, without depending on hardware-specific
kernels.
Mathematical background
The product and its layouts
For each batch , with , which costs floating-point operations. A measured time of µs per operation therefore corresponds to
An matrix stores element of batch at in row-major and in column-major layout; both maps are bijections from index triples onto , so every layout combination describes the same mathematical product.
Blocking and workspace
gemm_blocked partitions the iteration space into
blocks and each block into register tiles. One block touches an
block of , a panel of and an
block of :
For a cube , and : larger blocks reuse
each loaded value more often, until exceeds the cache that should hold it.
That trade-off is what tuning searches, and workspace_bytes is , used by
valid_candidate as a hard limit. The largest default candidate,
, needs bytes.11 K. Goto and R. A. van de Geijn, “Anatomy of high-performance matrix multiplication”, ACM TOMS 34(3), 2008.
Exactness of the blocked kernel
For every entry, gemm_reference computes
gemm_blocked starts each entry at in the first block, stores the
partial sum in (a double, so storing is exact), reloads it in the next
block and continues in increasing . It performs the same operations on the
same operands in the same order, so the two results are bitwise identical as
long as the compiler treats both loops alike (in particular, does not contract
one of them into fused multiply-adds). Packing only copies values. The
correctness check can therefore use tolerance for the built-in kernel.
A kernel that reorders the summation (a vectorized microkernel, a different loop order) is only close. With unit roundoff and , any order of summation satisfies22 N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM, 2002, §3.1.
so two such kernels differ by at most .
With entries from reproducible_matrix,
and the bound is : for , about .
That is the absolute tolerance to pass to validate_gemm for a reordering
kernel on these inputs.
Reproducible inputs
reproducible_matrix(seed, r, c, layout) runs the 64-bit linear congruential
generator
and maps each state to . The multiplier is and the increment is odd, so by the Hull–Dobell theorem the generator has the full period . The 32-bit value is reduced modulo 2001: since , 886 of the 2001 values are slightly more likely, by a relative . Entries are multiples of in . The logical matrix is generated in row-major order and then stored in the requested layout, so the layout does not change the values.
Tails
Edge tiles handle , or that are not multiples of the block sizes.
boundary_shapes returns , and : for a multiple of ,
these exercise a short tile, an exact fit and a one-row tail.
Design decisions
A portable scalar kernel
Problem. Real GEMM tuning depends on SIMD microkernels that differ per
target. Choice. One scalar blocked kernel, with microkernel_id as a label.
Why. The package demonstrates and tests the tuning machinery on every
backend; a harness can map the label to its own kernels.
Validate before measuring
gemm_is_correct runs the candidate and compares with the reference before
the candidate is ever timed. execute_candidate returns None for invalid
candidates or shapes instead of a result that would look like a fast, empty
multiplication.
Workspace as a hard constraint
The memory a candidate needs is computed from its parameters and checked against a limit before execution, so the search never selects a configuration that cannot run in the target’s memory budget.
Explicit timing scope
TimingScope and AllocationMode are part of the candidate and of its id, so
“compute only” and “end to end” results are never compared as if they measured
the same thing.
Serialized configurations
config_json writes everything needed to repeat a tuning run, with 64-bit
values as strings and a schema version mmkts_1. Together with a
GemmEnvironment it says where a tuned result is valid.
Correctness and invariants
gemm_blockedequalsgemm_referencebit for bit under the condition above; the tests check all eight layout combinations with tails.workspace_bytes(c) <= limitfor every valid candidate.enumerate_gemm_candidatesreturns the same 486 candidates in the same order, all valid for the limit 262144.reproducible_matrixdepends only on its arguments; the row-major and column-major results describe the same logical matrix.validate_gemmcounts every element of a length difference as a mismatch, so truncated results are never valid.
Alternatives rejected
- Target-specific microkernels. Not portable across MoonBit backends.
- Relative tolerances in validation. Entries of can be near zero; the absolute bound above is the honest one for bounded inputs.
- Generating inputs with the platform random generator. Not reproducible across targets.
Boundaries
- Only
m_n_kloop order and scalar code; the three microkernel ids run the same loop. pack_operandcopies a whole operand into row-major order; it does not build cache-sized packed panels.allocation_mode,timing_scopeandreuse_countare descriptive: the kernel allocates its result on every call; the harness decides what to time.- No search loop:
tuneand the application drive the search. reproducible_matrixmakes one matrix; batched operands are concatenated by the caller.