Call a kernel from C++
For generic application/framework preparation without operation-specific headers, use the cold named-request facade. It produces the same checked executable consumed by Runtime; parameter decoding never enters the execution path.
For direct A65 numerical calls, every kernel follows the lifecycle below. Parameters remain operation-specific C++ types; no JSON parser is involved.
- Describe every input and output with
TensorSpec. - Supply the operation's typed parameters.
- Call
check_inference()once for that metadata. - Handle
Error, or retain the returned immutablePlan. - Allocate/map real storage according to the plan's requirements.
- Pass borrowed buffer views to
execute()and handle its status. - Reuse the plan with new buffers, but prepare again when specifications or parameters change.
#include <simaai/kernels/a65/cast.h>
#include <array>
#include <cstdint>
#include <stdexcept>
int main() {
namespace k = simaai::kernels;
namespace op = k::a65::cast;
const std::array input_specs{
k::TensorSpec{k::Shape{2, 3}, k::DType::Float32}};
const std::array output_specs{
k::TensorSpec{k::Shape{2, 3}, k::DType::BFloat16}};
auto checked = op::check_inference(
{input_specs, output_specs, op::Params{}});
if (const auto* error = std::get_if<k::Error>(&checked))
throw std::runtime_error(error->message);
const auto plan = std::get<op::Plan>(std::move(checked));
// These arrays satisfy this example's checked requirements.
// General callers allocate from the plan's storage/alignment requirements.
std::array<float, 6> input{1, 2, 3, 4, 5, 6};
std::array<std::uint16_t, 6> output{};
const std::array inputs{k::ConstHostBuffer{
reinterpret_cast<const std::byte*>(input.data()), sizeof(input)}};
const std::array outputs{k::HostBuffer{
reinterpret_cast<std::byte*>(output.data()), sizeof(output)}};
if (auto error = op::execute(plan, inputs, outputs))
throw std::runtime_error(error->message);
}
Why provide an output specification?
The checker validates the caller's expected output against the operation. Even shape-preserving operations receive an explicit output shape and dtype. Storage requirements are a checked result of the specifications, not an unchecked size claim from the caller.
What happens on each execution?
Execution checks actual buffer arity, required spans, alignment and permitted aliasing before writing output. Operations with FP-mode requirements also check the applicable execution environment. Preparation is not permission to skip checks when binding new buffers.
A pointer and capacity cannot prove that memory is mapped or owned. The caller must supply accessible storage and keep it alive until the synchronous call returns. Runtime owns mapping, coherency, synchronization and resource lifetime; those services do not belong in a numerical kernel.
Reuse, concurrency and scratch
Independent calls may share immutable plans. They must not race on input or output storage. Operations with scratch, such as BoxDecode and SuperPoint, need a separate prepared context for each in-flight execution. Use the operation's context API; scratch is reused, not allocated each frame. Do not share a busy context between concurrent calls. Context creation and preparation can allocate and throw; successful execution is allocation-free.
See BoxDecode's reference for its multi-input, multi-output call and SuperPoint for its context lifetime. The support matrix lists backend availability.
Asynchronous Runtime integration
The numerical A65 execute() above remains a synchronous leaf operation. The
separate Runtime executor owns the asynchronous job boundary: prepare()
automatically calls canonical check_inference(), and submit() returns a
completion handle. Its synchronous convenience call is exactly submission followed
by waiting, not a second execution implementation.
Completion means computation and required memory synchronization have retired, not merely that a driver accepted the job. Runtime retains owners and mappings even if the caller drops the completion handle. Dependent work must observe both completion and failure; empty tensors skip numerical dispatch, not dependency ordering. Host pointers must never be passed as fabricated EV addresses.
Shared Cast, Dequant and Quant requests live in simaai/kernels/ops/cast.h,
simaai/kernels/ops/dequant.h and simaai/kernels/ops/quant.h; the caller selects
a target during preparation and Runtime verifies that target is available. EV use additionally needs an explicitly compatible native
entry and transport. Host codec or fake-transport tests alone do not establish
installed firmware support or device correctness.
Open the asynchronous lifecycle diagram. The caller receives a handle independently of job progress; successful completion follows hardware retirement and memory-access synchronization.
Prepare a generic executable
Use make_executable(request, target) when an execution service, rather than the
calling thread, will invoke the operation. The factory belongs to Kernels and
calls the same canonical validation as the direct API. It returns an immutable
Executable containing the tensor contract and a selected implementation—not
memory owners, a thread pool or a device connection.
// input_specs/output_specs remain explicit, as in the direct example above.
#include <simaai/kernels/ops/cast.h>
auto checked = simaai::kernels::ops::cast::make_executable(
simaai::kernels::ops::cast::Request{
input_specs, output_specs, {}},
simaai::kernels::Target::A65);
Runtime's generic prepare(executable) adds device-capability admission and
bounded execution resources. Its typed convenience overload finds the
kernel-owned factory through C++ argument-dependent lookup; adding an operation
does not add an overload or operation-name switch to Runtime. Callers must link
the operation's factory target. SimaKernels::executables is a convenience
aggregate; Runtime itself depends only on SimaKernels::execution.
Typed request → kernel validation → immutable executable
↓
Runtime admission/resources
↓
Current owned buffers → submission → completion after retirement
The executable retains its specifications after the original request is destroyed. Preparation and resource provisioning can allocate; successful numerical execution does not. A new buffer must still pass current-capacity, alignment, ownership and dependency checks. There is no automatic CPU fallback if an EV entry is unavailable.
Reuse the prepared work
| What changes? | Action |
|---|---|
| Values or buffers, with unchanged specifications and parameters | Reuse the plan; submit current views. |
| Stable allocations and mappings | Bind once and replay the bound operation. |
| Shape, dtype, layout or ordinary parameters | Prepare a new checked operation. |
| Geometry with a supported compatible variant | Create the immutable variant and reuse resources through the checked API. |
| Stable operation sequence | Prepare and replay the sequence. |
Do not persistently bind recyclable camera loans or mutate an in-flight plan.
Dynamic geometry and scratch
ops::boxdecode::with_image_size(executable, image_size) produces a checked immutable
BoxDecode variant. It shares model tables, tensor-contract storage and the exact
resource recipe with the original executable. Runtime can admit it with the
previous prepared operation to reuse compatible scratch. Existing invocations
retain their own geometry. Creating the variant can allocate its small owning
handle; it does not copy the tensor vectors or recreate the numerical context.
Generic consumers use the retained dynamic-variant interface
for BoxDecode image_size and Preproc geometry. Preproc rechecks the complete
new tensor/geometry contract; it does not claim compatible resources merely
because the output canvas is unchanged. Other specification changes use the
operation's factory. There is no arbitrary parameter patching or global shape
cache. Substantial scratch is provisioned for simultaneous active executions,
not every pending submission.
Kernels that use parallel host phases request a HostServices service during
resource creation. Runtime owns the worker lifetime and shared concurrency
budget. A task failure or exception drains already-started tasks before the
service returns. A kernel must not create its own thread pool.
Host publication callbacks
<simaai/kernels/host_authoring.h> exposes the opaque authoring::Contract,
ContractOptions, HostOptions and HostEntry vocabulary for generic Runtime
host-publication services. Consumers do not need private executable definitions.
The provider supplies its checked storage requirements and immutable publication
plan; this seam does not infer storage or validate an arbitrary callback's math.
Numerical operations still use their canonical factories. Keep GStreamer,
firmware structures and application configuration outside this vocabulary.
Package compatibility
This interface requires matching supported package builds; it is not a stable cross-version C++ binary ABI. EV execution additionally requires matching native firmware. The kernel package supplies operation registrations and descriptor rules; Runtime retains device submission, DMA authorization and completion. A successful host factory call does not establish device correctness or speed.