pub type KernelRef = fn(inputs: &[Arc<RwLock<Storage>>], outputs: &mut [Arc<RwLock<Storage>>], layouts: &[Layout], params: &OpParams) -> Result<()>;Expand description
Uniform function-pointer signature for per-backend op kernels.
Inputs are passed as a slice of &Storage to handle multi-input
ops (binary, ternary, custom). Outputs are passed as a slice of
&mut Storage to handle multi-output ops (topk, var_mean,
custom). Most ops use single-element slices.
layouts is a side-channel that parallels inputs.append(outputs)
— i.e. layouts[0..inputs.len()] are the input layouts in order,
followed by layouts[inputs.len()..] for the output layouts. This
is the load-bearing primitive for stride-aware kernels (broadcast,
transpose, gather-from-strided). Kernels that only support
contiguous-equal-shape inputs (today’s default) can ignore
layouts because the executor’s auto-Contiguize pass guarantees
every input is contiguous before this is called; the layout slice
then carries Layout::contiguous(shape) entries that are useful
for shape inference but redundant with Storage.len_bytes / dtype.
OpParams carries op-family-specific extras (reduce dims,
conv2d geometry, etc.). Most kernels match a specific variant;
mismatches are programming bugs that the dispatch resolver
must prevent. Pure layout/shape duplication that used to live in
OpParams (e.g. the old Reduce::input_layout) now flows through
layouts instead.
Output Storage is pre-allocated by the executor before the kernel is called. Kernels write into the pre-allocated bytes; they never allocate.
§Multi-output kernels (Option C, Session 5)
Multi-output ops (e.g. SelectiveScan returning (y, last_state))
emit ONE KernelRef. The contract:
outputs.len() == 1. The singleArc<RwLock<Storage>>is the producer’s bundled Storage — itsbundle()returnsSome(&[OutputView; N])describing each logical slot’sdtype/shape/layout/byte_offsetinside the bundle’s underlying byte buffer.- The kernel writes each logical output into its slot’s byte
range by acquiring a single write lock on the output and
striding by the slot’s
byte_offset. The bundle metadata is the authoritative per-slot spec —outputs[0].read().unwrap() .bundle().expect("bundled storage").get(slot_idx)is the canonical access path. - Per-slot dtype tags do NOT travel through the kernel’s
KernelDTypeskey (which describes inputs + the bundle’s primary dtype only); the bundle metadata IS the per-slot dispatch info. - The bundle is pre-allocated by the executor via
allocate_bundled_storage(device, &output_views_spec)(seefuel_backend_contract::storage::allocate_bundled_storage). Kernels never allocate; they only fill bytes.
Consumers of multi-output producers are NOT multi-output kernels —
they’re Op::View (zero-copy slot projection) or Op::ViewOwned
(independent slot buffer), which the executor handles directly
without invoking a kernel.
Production-correct: kernels return Result, never panic.