Binary Kernel Design#

Nineteen binary ONNX operators share one descriptor, broadcast-plan, and runtime-adapter architecture. The implementation is under onnx_light_cpu/impl/math/binary and the registered adapter is onnx_light_cpu/kernels/elementwise/binary_kernel.cc.

Prepared execution#

NodeProto
   |
   v
BinaryKernelDescriptor
(operator, opset, attributes, typed adapters)
   |
   +---- concrete input shapes and types
   v
BinaryBroadcastPlanCache (8-entry LRU)
   |
   v
immutable BinaryBroadcastPlan
(output shape, coalesced strides, loop family)
   |
   v
scalar or bulk typed function

BinaryKernelDescriptor is built once for the node. It validates operator-specific attributes such as Mod.fmod and BitShift.direction, resolves the output type, and binds scalar and optional bulk functions for the exact input/output type triple.

Dynamic shapes use a bounded eight-entry LRU cache. Its key contains a monotonic descriptor identity, input/output types, and both shapes; it never stores raw descriptor pointers. A miss constructs and validates a fresh plan, so stale strides cannot be reused.

Operators and traversal#

The manifest covers arithmetic (Add, Sub, Mul, Div, Mod, Pow), comparisons, logical operators, bitwise operators, BitShift, and PRelu. It is the source of truth for registered type signatures.

Mod tracks schema version 28 while retaining minimum opset 10 compatibility. Starting at opset 28, floating-point fmod=0 uses floor-remainder semantics: nonzero results and exact zeros take the divisor’s sign. The implementation adjusts std::fmod rather than forming a potentially overflowing quotient. fmod=1 retains truncated-remainder semantics, including the dividend’s signed zero. Earlier opsets still require fmod=1 for floating-point inputs. FLOAT16 and BFLOAT16 use the same scalar semantics through the existing SIMD-capable float conversion adapters.

After right-aligning shapes, the plan assigns zero strides to broadcast dimensions, removes unit dimensions, coalesces compatible adjacent dimensions, and selects one loop family:

Loop family

Purpose

Contiguous

Both inputs and the output advance linearly.

Left / right scalar

Broadcast one scalar while preserving operand order.

Repeated contiguous block

Reuse one broadcast value or block across a contiguous suffix.

Inner-vector / outer broadcast

Vectorize a contiguous inner region while advancing coalesced outer offsets.

General strided

Use the validated coalesced strides as the correctness fallback.

Offsets advance incrementally across outer blocks; division and modulo are not performed for every output element. Empty dimensions produce no work and read neither input.

Compute, scheduling, and safety#

Same-type common signatures bind bulk contiguous and scalar-broadcast kernels. Other legal signatures use typed scalar adapters. Validation occurs before unchecked loops for integer division, modulo, shifts, and other operations with invalid inputs. Comparison results and logical tensors use one byte per BOOL.

The tuning snapshot has separate thresholds for bulk, block-broadcast, and scalar layouts plus a target block size and participant limit. Independent flat ranges or outer blocks are submitted through the session executor; the plan itself owns no scheduler and stores no thread count.

An input may alias the output only when its type and shape equal the output and the selected traversal cannot overwrite a value before its final read. A broadcast input is never expanded in place.