Skip to main content

KernelRef

Type Alias KernelRef 

Source
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 single Arc<RwLock<Storage>> is the producer’s bundled Storage — its bundle() returns Some(&[OutputView; N]) describing each logical slot’s dtype/shape/layout/byte_offset inside 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 KernelDTypes key (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) (see fuel_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.