Skip to main content

Add a kernel

Add math in Kernels, not in Runtime. A kernel owns its tensor contract, ordered computation, checked preparation and implementation selection. Runtime supplies memory access, scheduling, device submission and retirement.

This is the canonical authoring guide. Backend skills describe optimization and qualification procedures; they do not define another API or folder template.

Follow production operations​

Production operations are the examples. There is no separate teaching kernel to copy or maintain. Inspect the operation closest to your requirements:

RequirementProduction reference
Simple conversion and shared A65/EV74 contractCast
Multiple ports and prepared parallel resourcesSuperPoint
Dynamic geometry and reusable scratchBoxDecode
EV codec, registration and device executionHistogram
Specialized image-processing pathsPreproc

Keep a single-backend A65 operation together:

A65/<name>/
CMakeLists.txt
<name>.h # Installed as simaai/kernels/ops/<name>.h
request.cpp # Cold named parameters -> typed request
prepare.cpp # Admission and checked executable factory
kernel.cpp # Numerical entry; specialized workers may be separate
tests/{correctness,latency}.cpp
docs/index.md

An EV operation has explicit host/device boundaries:

EV74/<name>/
CMakeLists.txt # License-free host build
<name>.h # Public semantic request and checked plan
request.cpp # Cold named parameters -> typed request
prepare.cpp # Semantic admission
host/
codec.h # Installed as simaai/kernels/ev74/<name>.h
codec.cpp # Device representability and packet binding
executable.cpp # Kernel-owned executable factory
route.h # Trusted route schema
device/
CMakeLists.txt # Licensed compiler build
... # Native entry, ABI/control headers and workers
firmware/
Native<Name>Firmware.cmake
registration.{h,cc}
tests/
CMakeLists.txt
correctness.cpp
e2e/CMakeLists.txt
docs/index.md

For an operation supported on both backends, put its one semantic contract in common/<name>/<name>.h, admission in common/<name>/prepare.cpp and factory in common/<name>/executable.cpp. Its cold named decoder lives in common/<name>/request.cpp. A65's backend.h is the direct numerical API; EV's host/codec.h is the device codec. Keep backend implementations, tests, documented calls and mathematical execution notes in their respective operation folders. Do not duplicate semantic validation to make two folders look identical.

File responsibilities are consistent; file counts need not be. Keep substantial SIMD/DMA workers in qualified separate translation units. Add a private header only when it has a real user. A small operation may keep its factory with preparation instead of adding an otherwise empty layer.

Add the operation​

  1. Define the public request and numerical contract before implementing the math.
  2. Add the backend's explicit sources using the same build-default helpers as the production operation you inspected. Preserve required FP flags and tuning.
  3. Add request.cpp beside the semantic public header. Decode only the operation's named fields with common/request/reader.h, reject unknown/duplicate/type/range errors, and call the existing typed make_executable(). Do not duplicate numerical admission or interpret parameters during execution. See the named-request contract and production decoders.
  4. Register membership once: A65 in cmake/A65Operations.cmake; EV in the local firmware/Native<Name>Firmware.cmake declaration. The firmware composition guide defines readable IDs, generated membership and released compatibility. Do not edit generated catalogues or change the released compatibility snapshot to add an operation. Use sima_register_operation() for package ownership.
  5. Add independent correctness cases, measured latency cases and the canonical mathematical page with a public-call snippet and links to the real tests.
  6. Run the corresponding host, installed-package and target checks. The installed Runtime consumers (tests/runtime_consumer/README.md) exercise production operations across the repository boundary; Runtime source changes are not part of adding ordinary math.

For EV tests, keep routine CPU checks bounded to admission, binding, dispatch, independent layout goldens and instrumented source-safety mechanisms. Put broad numerical coverage in the real device fixture and retain exhaustive CPU sweeps in full mode for weekly/manual validation. Reuse the existing test executable and profile completion records; do not add one binary per case. Follow the profile policy.

Define the contract before the fast path​

  1. Define a typed Request in simaai::kernels::ops::<name>, with explicit input and output specifications and typed parameters. Do not add a shared central operation enum. The request type identifies the operation; parameters select its supported mathematical variants.
  2. Document ordered port roles, supported rank/dtype/layout combinations, parameter defaults and physical storage requirements. Six-axis capacity does not mean every kernel accepts every rank, stride or layout.
  3. Implement canonical check_inference(). Check arity, shape, output agreement, parameters, overflow and implementation limits. Retain owned metadata, not spans into a temporary caller request.
  4. State the math: intermediate precision, operation order, rounding, saturation, exceptional values, ties, padding and output initialization. Fusion does not authorize FMA or a different rounding boundary.
  5. Define binding checks separately. Future addresses and capacities cannot be validated during metadata preparation. Check them before the first write; explicitly declare aliasing and workspace requirements.

The direct numerical entry remains useful for kernel testing and applications that already manage execution. Keep its name/signature operation-specific and its checked contract clear. Do not make direct callers construct Runtime queues, GStreamer buffers or application configuration.

Connect the same math to Runtime​

Declare make_executable(const Request &, Target) in the request's namespace. Runtime can find it by argument-dependent lookup. Typed applications include <simaai/kernels/ops/<name>.h> and link the operation target explicitly alongside SimaRuntime::runtime when using the runner.

Applications without operation-specific headers use KernelRequest from <simaai/kernels/request.h> and link SimaKernels::request. The resolver is emitted from sima_register_operation() registrations and calls the operation's decode_request() once. Adding ordinary math does not add a Runtime switch or a plugin integration file. Both entry points produce the same executable; there is no generic interpreter in numerical or submission loops.

Typed request
-> check_inference + backend admission
-> owned checked executable
-> Runtime prepare: capability and bounded resources
-> bind current buffers / submit
-> numerical completion
-> Runtime coherency and retirement

The factory selects a numerical function or EV packet-binding function once. It must not move operation-specific decisions into Runtime or repeat selection inside every element's inner loop. A new ordinary operation requires no Runtime operation overload, switch or registration patch.

Use <simaai/kernels/authoring.h> for ordinary factories: make_contract() copies checked requirements; stateless_host() reuses a direct checked entry; context_host() adds cold per-in-flight resources; ev_executable() publishes a checked codec binding entry. EV entry names must be static literals or backed by retained immutable payload storage, never temporary strings. Do not copy Runtime internals. The provider implementation interface remains reviewed kernel code: never publish unchecked state or let application parameters assert that resources are compatible.

Empty inputs, outputs and scratch​

  • Mark an invocation as no work only when all required output stores can be omitted. Empty candidates can still require zero counts, statuses or padding.
  • Allocate bounded reusable scratch during preparation, or declare explicit per-in-flight workspace. Successful numerical execution must not allocate.
  • Resource creation is a cold operation that can fail. Report expected failures through Result/Error; cold allocation can still throw std::bad_alloc, as in the direct API. Result is not a noexcept guarantee. Do not publish a partially initialized plan, silently create private threads or fall back to another device.
  • Share prepared resources only through their exact kernel-owned compatibility recipe. Dynamic geometry does not by itself establish scratch compatibility.
  • A timeout is not retirement. Runtime retains storage until completion or confirmed recovery; kernels must drain their own DMA events before returning.

The installed Runtime consumers (tests/runtime_consumer/README.md) run real SuperPoint and submit real Histogram descriptors using a recording transport. They test package compatibility, not an independent extension ABI. Recording transport tests do not qualify EV numerical execution.

Add the EV contribution without changing Runtime logic​

An EV kernel needs all of these pieces; host admission alone is not execution:

  1. Codec: encode explicit little-endian fixed-width fields, a version and supported payload length. Leave relocatable addresses unbound. Validate the actual bound regions and their agreement with the prepared plan. Runtime provisions packet capacity; the binder fills its entry, ABI, payload length and bytes without reallocating. Do not assume reserved storage already contains a copied packet.
  2. Device admission: reject malformed payloads before launch. Specify which input-content checks run on-device and whether an error can leave partial outputs. Host metadata checks cannot replace these checks.
  3. Workers: implement math, DMA waits, local-memory budgets, tails and physical write ownership. Use the pinned dispatcher's group IDs and launch geometry.
  4. Registration: contribute the capability probe and numerical entry to Kernels-owned firmware composition. Preserve explicit payload variants; registrations are not all fixed-size copies of one another.
  5. Trusted schema: put a self-contained host/route.h in EV74/<name>/. Define native_route and NativeProbe in simaai::kernels::ev74::<name>. The route declares admitted payload sizes, authorized address slots and inactive slots; probe layout is checked at compile time. cmake/GenerateNativeSchemas.cmake assembles these contributions into the installed trusted catalogue without changing Runtime or the existing route order. Do not edit its generated headers. Applications do not supply authorization schemas; Runtime retains driver and DMA-BUF safety checks.
  6. Build inventory: declare NAME, a readable decimal GRAPH_ID and PACKAGE in the local firmware/Native<Name>Firmware.cmake. The composition guide defines accepted families, generated outputs and selection. Do not edit a central membership table, generated headers or the released compatibility snapshot to add an operation. The build rejects duplicates, invalid intervals and stale installed outputs. Production device/CMakeLists.txt and tests/e2e/ remain explicit.

Adding an EV operation requires rebuilding the Linux CVU adapter against the updated trusted catalogue and composing matching firmware. No Runtime source change is required; an already-built Runtime binary does not discover arbitrary new EV routes. This is not a stable cross-version C++ plugin ABI. An unknown or unprobed route must fail before submission.

Register the build and keep its evidence reproducible​

Use sima_register_operation() from local CMake with the conventional <name>.h, docs/index.md and tests/ paths shown above. For a single backend, the numerical target is simaai_kernels_<backend>_<name> (lowercase backend). A dual-backend contract registers BACKEND SHARED from common/<name>/, with the checked factory target simaai_kernels_<name>_executable. Shared membership is derived from the two backend catalogues, not maintained in a third list. All operations expose SimaKernels::<name>. The registration helper installs the public header and installs/exports both the numerical target and its facade exactly once. Do not add a second numerical-target install declaration. Extra public headers and dependency targets still need explicit install/export declarations; the helper does not guess sources, numerical flags or private dependencies. Keep the public header self-contained: private adjacent includes do not become public automatically. A65 membership lives only in cmake/A65Operations.cmake; EV membership is derived from operation-local firmware declarations. tools/operation_inventory.py feeds downstream tools. EV tests keep an explicit tests/CMakeLists.txt with their required compiler flags and host-versus-device distinction; the build must not silently treat a host fixture as device qualification. Keep source lists, dependent kernels, FP flags and OpenCL header dependencies visible in the operation's build definition. The device acceptance project is tests/e2e/, builds <name>_cvu_e2e and links the installed operation facade and SimaRuntime::runtime. Shared test utilities may use the package's version-matched SimaKernels_TEST_SUPPORT_DIR; this is test infrastructure, not an application API. Numerical E2E ownership stays with the operation, while Runtime owns generic transport, dispatch, lifetime and recovery tests. Do not infer them from a directory scan.

Use canonical CMake targets for production tests and qualification. Copying a convenient operation's compiler command can silently omit another operation's required FP flags and change its independent oracle. Host builds must remain license-free; private builds verify the pinned runtime/toolchain cohort and archive source identity before firmware composition.

Preserve physical firmware link order, specialized translation units and per-source compiler options. Even a source-preserving relink can change latency.

Test and document the complete contribution​

Keep one parameterized correctness executable and one latency executable per A65 operation. EV qualification may combine both modes in its operation-local device fixture. Add cases, not one executable per shape. Keep stable case IDs and independent expected values active in Release builds. Production latency suites use the standard benchmark support: --list-cases, suite/case selection, checked execution timing, separate preparation/first-call costs and percentile reports. Do not replace it with a mean-only timer or count catalogue listing as a latency measurement.

Production inventory checks enforce local contribution completeness. The root package_consumer check installs and relocates the real package, compiles each public operation header and links real factories/codecs through their exported targets. Documentation checks validate authored call snippets and links to real operation tests; they do not compile the snippets. These checks require no teaching operation and do not imply target performance.

Cover admitted contracts, edge shapes, tails, numerical boundaries, malformed specifications and packets, invalid bindings, no-write rejection where promised, reuse, concurrent scratch ownership and successful-path allocation behavior. Retain independent layout/graph-ID goldens; production-generated expectations and round trips are supplementary checks, not independent oracles.

Time the checked execution path. Separate preparation/first-call costs, validate outside timed regions and preserve the median qualification policy. Use canonical standalone device targets linking both Runtime and the operation, not accidental transitive numerical dependencies.

Write the ordered equations, path eligibility, memory movement and complete public call snippet in the operation's canonical page. Do not add standalone demonstration programs or a snippet build framework. Link that page from the support matrix and native catalogue. Follow the documentation guide.

Finish with installed-package consumer checks and the appropriate target tests. Report host contracts, source-model tests, private compilation, actual device correctness and latency qualification separately. An x86 pass or successful firmware link is not EV execution evidence.