EXTENDING HYPERFOLD
Register custom kernels.
From version 0.1.1Use Python callbacks for prototyping, orchestration and tests. Declare an operation contract, register exact backend signatures, then seal them into a context.
OperationSpec owns operand roles and access modes.
KernelSpec supplies a backend-specific signature and callback. Execute
the finished operation with a mapping keyed by OperandRole; outputs are
caller-owned.
PYTHON CALLBACKS
Declare, register and execute an operation.
Python callbacks are synchronous and suit prototypes, orchestration and tests. The callback receives the original caller-owned objects in an immutable role mapping and writes every declared output.
import numpy as np
import hyperfold as hf
operands = (
hf.OperandSpec(hf.OperandRole.INPUT_0, hf.OperandKind.VECTOR),
hf.OperandSpec(
hf.OperandRole.OUTPUT,
hf.OperandKind.VECTOR,
hf.OperandAccess.WRITE,
),
)
def double(invocation: hf.Invocation) -> None:
source = invocation.bindings[hf.OperandRole.INPUT_0]
output = invocation.bindings[hf.OperandRole.OUTPUT]
output[:] = 2 * source
backend_operands = (
hf.BackendOperandSpec(hf.OperandRole.INPUT_0, hf.OperandKind.VECTOR),
hf.BackendOperandSpec(
hf.OperandRole.OUTPUT,
hf.OperandKind.VECTOR,
hf.OperandAccess.WRITE,
),
)
builder = hf.ContextBuilder()
extension = builder.extension("acme")
extension.register_operation(hf.OperationSpec("acme.double", operands))
extension.register_kernel(hf.KernelSpec(
"acme.double", hf.BackendKind.CPU_SCALAR, backend_operands, double
))
context = builder.build()
source = np.array([1, -2, 4], dtype=np.float32)
output = np.empty_like(source)
result = context.execute(
"acme.double",
{hf.OperandRole.INPUT_0: source, hf.OperandRole.OUTPUT: output},
)
assert result is outputInputs must be contiguous, ordered, read-only roles followed by contiguous mandatory write or read-write outputs. The builder rejects gaps, duplicates, reordered roles and access mismatches. A single-output call returns the supplied Output0 object; a multiple-output call returns the supplied output objects as an ordered tuple.
DISPATCH CONTRACT
Python kernels match exact storage.
BackendOperandSpec registers an exact backend, dtype, compute dtype,
location, layout and contiguity signature. Python callbacks do not use native
adaptation. Pass an optional role-to-dtype mapping as compute_dtypes= to
Context.execute() to select a registered storage/compute pair without
casting storage. The callback reads the chosen values from
Invocation.compute_dtypes.
Validators receive an immutable binding map before dispatch and return
None; raise an exception to reject the call. Callback exceptions propagate
to the caller. A CUDA callback receives the selected device and Hyperfold stream
pointer through Invocation.cuda_device and Invocation.stream;
Hyperfold synchronises that stream before returning.
A builder is mutable until build() and may be built only once. The finished
context owns its registered Python callbacks. See the
Python extension reference for every builder,
descriptor, enum and numerical metadata record.
COMPILED KERNELS · DEVELOPMENT ABI
Use a compiled kernel from Python.
Build your kernel as a shared plug-in, then load it into the context that will execute it. Python only selects and loads the plug-in; the shared library owns the native CPU or CUDA implementation.
-
01
Build the shared plug-in
Compile against the public headers exposed by the installed Python package. A C ABI plug-in does not link against Hyperfold.
-
02
Load it into a context
Pass the plug-in path to
ContextBuilder.load_plugin()before callingbuild(). -
03
Inspect and execute
Use
operations()anddescribe()to check registration, then execute the operation through the context.
Compile your C11 plug-in as a shared module against the public headers supplied by the Python package. The plug-in does not link against Python or Hyperfold; match the host ABI, operating system, architecture and runtime.
# CMakeLists.txt
add_library(acme_plugin MODULE acme_plugin.c)
target_include_directories(acme_plugin PRIVATE
"${HYPERFOLD_INCLUDE_DIR}")
# Supply HYPERFOLD_INCLUDE_DIR from Python:
# python -c "import hyperfold; print(hyperfold.get_include())"
Load the compiled library before building the context, then inspect its registered operation to confirm that the plug-in was accepted.
from pathlib import Path
import hyperfold
context = (
hyperfold.ContextBuilder()
.load_plugin(Path("build/libacme_plugin.so"))
.build()
)
description = context.describe("acme.my_kernel")
print(description.implementations)
Use the actual CMake output path for your platform; Windows modules use .dll.
WHAT THE PLUG-IN MUST PROVIDE
Export hyperfold_plugin_init, return plug-in metadata, and register
kernel descriptors through host->register_kernel. The first kernel defines
the operation contract; subsequent implementations must agree on roles, access and kinds.
Check the host structure size and exact HYPERFOLD_PLUGIN_ABI_REVISION
before registration; rebuild plug-ins against the current headers. Use
hyperfold/plugin/abi.h for C11, or
hyperfold/plugin/sdk.h for C++17 helper utilities.
- Callbacks check operation-specific shapes after adaptation and before writing outputs or launching work. CPU callbacks complete their output before returning. CUDA callbacks enqueue work on the stream supplied by Hyperfold.
-
Do not let C++ exceptions cross an ABI callback; use
Hyperfold::Plugin::translate_exceptionswith the C++ helper. - The context retains the library while its operations or executions still need it.
The complete plug-in ABI reference documents every C type and constant, callback lifetime, CPU and CUDA execution rules, CMake setup, and complete C and C++17 entry-point examples.