Skip to content

Buffer execution

The execution API provides an explicit route for CPU matrix products on caller buffers. Include hmath_execution.h in C/C++; in Python use from hmath import execution. It supports binary32 and binary64 and row-major matrices with consecutive columns and optional padding between rows.

This API is separate from the original dense matrix and tensor interfaces. It does not change their arithmetic, limits, finite checks or failure behavior. It is not a claim that the complete HMath.Backend API has been bound.

Provider and resource selection

hmath_execution_create(options, &out) constructs an immutable execution handle. A NULL options pointer selects the reference implementation with no artificial resource caps. Python uses execution.Execution(...).

Option Meaning Default
provider REFERENCE, OPENBLAS, BLIS, MKL, or ACCELERATE; Python also accepts lowercase names REFERENCE
integer_bits BLAS integer ABI: 32 for LP64 or 64 for ILP64; zero selects LP64 in C 32
library_path Optional explicit provider library path; incompatible ABIs are rejected NULL / None
max_elements Maximum logical element count of each A, B and C view 0, no artificial cap
max_multiply_adds Maximum m×n×k work count per call 0, no artificial cap

A requested provider never silently falls back when unavailable. The reference path does not require OpenBLAS or another BLAS installation. Single-precision SGEMM is an optional provider capability: if missing, FP32 execution returns HMATH_UNSUPPORTED; double-precision DGEMM can remain available. No conversion to FP64 is used as a fallback. Existing HMATH_BACKEND provider policy also applies.

Checked dimension products, byte spans, pointer ranges and allocation failures remain active when resource policies are zero. Provider calls must also fit the selected LP64/ILP64 integer ABI, including all leading dimensions. These caps are policies for logical sizes/work, not a guarantee of a fixed process heap budget. The reference Haskell implementation needs temporary lists/matrices; provider execution passes buffers directly without numeric staging allocations. The legacy tensor limits of 1,048,576 elements and 16,777,216 multiply-adds do not apply to this new API.

Execution handles own provider library references. C callers use hmath_execution_retain/release; NULL retain/release is a no-op. Python callers use close() or a with block. A handle alone does not pin the Haskell runtime, but each numerical call reserves it until execution finishes. Each concurrent caller must hold a live handle reference and use an independent output buffer. Provider interface-layer settings must not be changed concurrently by external code; this is particularly relevant to MKL's process-wide integer ABI.

Views and GEMM

hmath_execution_view describes a readable input; hmath_execution_output describes a writable output. Both contain:

Field Contract
data Pointer aligned for the selected float32/float64 type
storage_count Readable/writable backing count in elements
rows, columns Logical dimensions
leading_dimension Distance between row starts in elements, at least columns for a nonempty matrix

Columns must be consecutive. Negative strides, column-strided layouts and transposition are not supported by this entry point. Empty shapes preserve their metadata and need no data pointer. For a nonempty view, the reachable span is (rows - 1) * leading_dimension + columns; it is checked against the backing count without overflowing. The caller guarantees that the actual allocation matches the described span. Arbitrary addresses and allocations cannot be validated by a C library.

hmath_status hmath_execution_gemm_f32(
    hmath_execution *execution, hmath_context *context,
    float alpha, const hmath_execution_view *a,
    const hmath_execution_view *b, float beta,
    const hmath_execution_output *c);

hmath_status hmath_execution_gemm_f64(
    hmath_execution *execution, hmath_context *context,
    double alpha, const hmath_execution_view *a,
    const hmath_execution_view *b, double beta,
    const hmath_execution_output *c);

Both compute C := αAB + βC. A has shape m×k, B has shape k×n, and C must have shape m×n. A and B may overlap each other; the complete reachable output span, including padding between rows, must not overlap either input span. Padding is preserved. Inputs and metadata are borrowed for the synchronous call and must remain live and unchanged, including through aliases.

Runtime initialization is required. A NULL context disables cancellation; cancellation is checked before and after execution. An in-flight BLAS call is not interrupted.

The reference path uses the existing Haskell Matrix.multiplyChecked, scaleMatrix and addMatrixChecked at the selected Float/Double precision. Optimized providers execute SGEMM/DGEMM on the supplied pointers and may use a different reduction or FMA order. Numerical results can therefore differ in their last bits across providers; selecting an optimized provider is explicit.

Finite values and output on failure

The finite-input policy matches the existing checked Haskell Backend.gemm: α, β and every logical A/B/C element must be finite, including initial C when β = 0. Padding values are not numerical inputs. NonFiniteVectorEntry i is reported through HMATH_MATH_ERROR / Python HMathError(status=11), with indices in [alpha,beta] ++ logical(A) ++ logical(B) ++ logical(C). Output errors index logical C. Shape, byte-span, alias, resource-limit and provider errors are binding-level status codes.

Validation errors preserve C. Once a kernel starts, numeric failure or cancellation may leave C modified. Direct-output execution is not transactional. The reference path calculates and checks its result before copying to C; an optimized provider writes C directly and a later finite check may fail. A post-kernel cancellation can also report failure after C has been written.

Python buffers

Execution.gemm(a, b, out, *, alpha=1, beta=0) borrows NumPy arrays and returns the same out object. Arrays must have the same native-endian float32/float64 dtype, aligned storage, consecutive columns and nonnegative row strides. out must be writable. Padded rows are accepted without copying. Unsupported layouts and mixed dtypes are rejected rather than silently converted.

Execution.matmul(a, b) is a convenience method that allocates a zeroed output of the input dtype, then executes GEMM. Use gemm with a reusable output to avoid that allocation. NumPy is an optional Python dependency; the C execution API does not depend on NumPy.

import numpy as np
from hmath import execution

with execution.Execution("reference") as engine:
    a = np.array([[1, 2], [3, 4]], dtype=np.float32)
    b = np.array([[2, 0], [1, 2]], dtype=np.float32)
    out = np.zeros((2, 2), dtype=np.float32)
    assert engine.gemm(a, b, out) is out
    assert out.dtype == np.float32
    assert out.tolist() == [[4.0, 4.0], [10.0, 8.0]]

See also: dense matrix contracts, numerical conventions, C runtime and errors.