Skip to main content

BoxDecode

Experimental C++20 API, package version 0.1.0. Link SimaKernels::a65_boxdecode. The caller contract below describes supported behavior; implementation notes describe this release, not a stable dispatch or binary ABI promise.

Header: simaai/kernels/ops/boxdecode.h (A65/boxdecode/boxdecode.h). Operation IDs are recipe-specific; use the catalogue below.

Contract​

Choose a recipe from the detection catalogue, then check its exact export geometry and activation. Additional details: YOLO heads, SSD, and pose, segmentation and proposal outputs. Matching a model-family name alone is not sufficient admission evidence.

BoxDecode scan, selection, geometry, auxiliary outputs and publication

Open diagram at full size.

This is an experimental A65 API for explicit recipes across the families implemented by this kernel. It does not recognize arbitrary model exports by name, migrate plugins, or promise bounded real-time latency. Successful execution reuses prepared scratch without heap allocation. Header: simaai/kernels/ops/boxdecode.h; target: SimaKernels::a65_boxdecode. Every recipe uses the same check_inference → Plan → ExecutionContext → execute API, independent borrowed inputs, and explicit output tensors.

Geometry uses an explicit centered-letterbox inverse for YOLO, CenterNet and proposals, or Stretch for SSD/DETR, followed by original-frame clipping. It does not infer stretch, asymmetric padding or cropping from model names or environment variables. Those need separately qualified transform recipes.

The four common outputs have explicit contiguous tensor specifications; tasks with additional output use the fifth-output schemas:

PortTypeShapeMeaning
0INT32[K,4]Rounded original-image x,y,width,height.
1FP32[K]Confidence.
2INT32[K]Class ID.
3INT32[1]Produced count, in [0,K].

INT32 here represents coordinates/indices/count, not an MLA activation input. Coordinates are rounded, clipped original-image x,y,width,height, not continuous FP32 XYXY values. Only the valid prefix describes detections; unused capacity is zeroed deterministically, including on a reused output.

Parameters​

Configure the request with these typed parameters:

  • model_input_size = {.width = ..., .height = ...} and original_image_size = {.width = ..., .height = ...}. These describe model and final detection coordinate spaces; they are not prediction tensor shapes. Resize/letterbox policy remains explicit.
  • max_detections: the final output limit K, not a pre-NMS candidate limit. K must be in 1..65536; zero is not an uncapped-output sentinel. Caller-supplied output shapes must agree with it.
  • detection_threshold: a finite value in [0,1), compared inclusively with the activated confidence. Zero is a numerical cutoff, not a request to choose a default. nms_iou_threshold must be finite and in [0,1]; its zero policy is recipe-specific as described below.
  • quantization: a sparse std::map<std::size_t, Quantization> keyed by input port. Leave it empty for BF16-only inputs. Every INT8 port needs an entry; entries for BF16 or nonexistent ports are rejected. No scale/zero point is inferred or defaulted for INT8.
  • No public encoding field: the operation recipe determines it.
  • score_activation can be omitted for fixed recipes: YOLOv5/v7 and RCNN use Sigmoid, DETR uses Softmax, and SSD uses its selected recipe's activation. An explicit matching value is accepted; a conflict is rejected. Other recipes require explicit Identity or Sigmoid, including when activation was folded upstream. No data-dependent detection of score representation is performed.

For example, if only input port 3 is INT8:

params.quantization = {
{3, {.dequant_scale = 0.03125f, .zero_point = -7}}
};

Plan::params() exposes the resolved activation and an owned copy of parameters. Preparation does not mutate the caller's request. Quantization is incorporated into prepared readers/tables; the sparse map is not searched per frame.

Numerical behavior​

INT8 requires one per-input Quantization{dequant_scale, zero_point}:

value = (raw - zero_point) * dequant_scale;

Scale is positive and finite; zero point is in [-128,127]. Interior score thresholds whose quantized cutoff exceeds supported integer bounds are rejected. Sigmoid's zero endpoint uses the actual prepared score table, without evaluating an infinite inverse-logit or converting it to an integer. BF16 inputs must omit integer quantization. dequant_scale is a multiplier. Convert any upstream reciprocal-scale convention before constructing these parameters.

Candidate selection and ordering​

Candidate ordering and final selection follow this policy:

  • A finite activated score equal to detection_threshold is eligible. At zero, Identity still rejects negative scores; Sigmoid/Softmax use their actual FP32 results, including a finite zero produced by underflow. INT8 cutoffs come from the prepared activated-score table and BF16 cutoffs from its actual FP32 score lattice, not an epsilon clamp. Geometry, foreground selection and applicable positive-objectness checks still apply.
  • K is final output capacity, not a predecode/pre-NMS candidate cap.
  • Candidates are visited by descending finite confidence; equal confidence uses ascending head index, row-major cell, anchor, then class.
  • Geometry is decoded lazily and validated before admission. Class-aware greedy NMS retains the integer IoU×256 and strict > comparison. Suppressed/invalid high-scoring candidates do not exclude lower-scoring valid survivors.
  • YOLO26 detection, pose and segmentation skip NMS at zero and use ordinary class-aware NMS at a positive threshold. Other NMS recipes still suppress at zero when their integer IoU×256 is greater than zero; overlap below 1/256 after integer corner/area rounding does not suffice. Zero does not universally disable NMS.
  • Stop after K accepted detections or exhaustion. DETR/CenterNet have no NMS and use geometry-validated streaming top-K.
  • Results are score-sorted with the same tie rule. Class reductions choose the first logical class at equal comparison keys. General BF16 reductions treat negative and positive zero equally. Dense BF16 MobileNet SSD is an exception: its raw-key reduction ranks +0 above -0, so a later +0 class can win over an earlier -0 class before sigmoid. Packed MobileNet SSD uses the general signed-zero tie rule.

Content safety​

  • Discard a candidate with nonfinite final confidence before sorting. Do not fall back to its next class. Finite results such as sigmoid(+infinity)=1 remain admissible.
  • Multi-anchor INT8 class-tail padding uses -128 and never participates as a logical class; first logical class wins ties.
  • Before NMS, discard nonfinite/degenerate geometry, float area below 0.5 or above the signed-int area range, or model-space corners outside ±10,000,000.
  • NMS corners use INT32 and widened integer arithmetic with a guarded union denominator. Nonpositive union gives zero IoU. The four-lane path uses an exact division-free inequality, not floating-point approximation.
  • Decoded single-anchor objectness is a separate input lane, not a class.
  • DETR uses stable softmax over logical channels, excluding the final background class from selection. Discard a cell containing a nonfinite logit. SSD lazy NMS uses the same geometry/IoU guards as eager NMS.
  • CenterNet scans every logical class and uses explicit grid-to-model projection.

Content checks are fused into processing; metadata validation is not a full-input scan. Both preparation and execution require nearest FP rounding, gradual FP32 underflow and masked traps. On A65, FPCR FZ and FZ16 must both be clear: the half-flush check applies unconditionally, even for an INT8/BF16 recipe with no FP16 input. The kernel rejects unsupported state rather than changing it.

C++ call​

Place the includes and namespace aliases at file scope; the remaining statements belong in a simaai::kernels::Status-returning caller function. input_views and output_views are caller-supplied arrays of ConstHostBuffer and HostBuffer in port order, with accessible capacities and alignment from the checked plan. Keep their storage alive until synchronous execution returns. Handle allocation exceptions during preparation/context creation at the caller boundary.

#include <utility>
#include <simaai/kernels/ops/boxdecode.h>
#include <array>

namespace k = simaai::kernels;
namespace op = k::ops::boxdecode;
constexpr int capacity = 16;
// Six logical dimensions: singleton outer axes, H=3, W=5, C=4 or 17.
const std::array input_specs{
k::TensorSpec{{1, 1, 1, 3, 5, 4}, k::DType::BFloat16},
k::TensorSpec{{1, 1, 1, 3, 5, 17}, k::DType::BFloat16}};
const std::array output_specs{k::TensorSpec{{capacity, 4}, k::DType::Int32},
k::TensorSpec{{capacity}, k::DType::Float32},
k::TensorSpec{{capacity}, k::DType::Int32},
k::TensorSpec{{1}, k::DType::Int32}};
op::Params params;
params.profile = op::Profile::Yolo26V1;
params.heads = {{0, 1}};
params.model_input_size = {.width = 40, .height = 24};
params.original_image_size = {.width = 40, .height = 24};
params.num_classes = 17;
params.max_detections = capacity;
params.detection_threshold = 0.5f;
params.nms_iou_threshold = 0.5f;
params.score_activation = op::ScoreActivation::Sigmoid;
// BF16 ports require no quantization parameters.
auto checked = op::check_inference({input_specs, output_specs, params});
if (const auto *error = std::get_if<k::Error>(&checked)) return *error;
const auto plan = std::get<op::Plan>(std::move(checked));

auto context = op::create_context(plan); // One per in-flight call.
if (!context) return k::Error{k::ErrorCode::InvalidParameter, "context",
"context preparation failed"};
return op::execute(plan, *context, input_views, output_views);

See the correctness tests (A65/boxdecode/tests/correctness.cpp) for concrete storage, independent expected results and failure cases.

See calling kernels for the shared lifecycle.

Memory and lifecycle​

  1. check_inference(Request) validates operation, tensor specifications, roles, parameters, quantization and outputs. It returns an owning immutable Plan or a structured error. It also prepares immutable readers and numerical tables; preparation can allocate. No JSON, ConfigManager, environment inference, global instance ID, input-data probe, or actual buffer is needed.
  2. create_context(plan) allocates reusable candidate, reduction and output scratch from metadata-derived bounds, independently of the first frame's occupancy. Use one context per in-flight call. Copies of the same plan share its immutable metadata; independently prepared plans cannot exchange contexts.
  3. execute(plan, context, inputs, outputs) validates every actual view's size, pointer, scalar alignment and address range, and all output overlaps, before reading inputs or writing outputs. Concurrent use of one context is rejected with a nonblocking lock; separate contexts may share a plan.
  4. Inputs are independent, borrowed CPU-visible views. Read-only input aliasing is allowed; outputs must be mutually disjoint and disjoint from all accessed input ranges. Callers retain memory ownership/mapping until synchronous return. Addresses are unbound on completion or exception.
  5. Runtime owns DMA-BUF mapping, cache/coherency operations, buffer allocation and scheduling. Execution does not create or activate a worker pool.

Allocation contract: successful execute calls do not allocate, including first execution and empty-to-crowded transitions after context creation. Plan and context creation can throw std::bad_alloc. NMS recipes reserve compact 12-byte candidate records for their admitted per-head candidate bound; DETR/CenterNet use validated streaming top-K storage. Packed score reductions and SSD scan scratch are reused. Admission limits are not a guarantee that every shape fits device memory. Preparation memory/time must be budgeted separately. Identical INT8 softmax pair tables are shared within a plan when scale and zero point match; there is no process-global cache. Accepted-box NMS scratch additionally uses six INT32 arrays (24 bytes per output slot).

There is no caller-supplied byte workspace yet. Removing allocation does not bound candidate-dependent NMS work, scanning, or mask output bandwidth. Output publication occurs after successful decoding. Dense records are fully overwritten and unused rows zeroed; mask slots remain fully initialized, including outside each crop. INT8 masks use SDOT only when the preparation-time CPU capability check admits it; other CPUs retain the INT16-widening implementation. Both paths preserve integer accumulation and apply the scales before sigmoid. Live mask crops are computed and their complement cleared; unused mask slots are zeroed.

Physical input storage​

Coordinate and score inputs use the layouts below, subject to profile-specific bounds. YOLO26/6 RawLTRB coordinates use the same prepared C-block reader as other profiles; no separate detessellation or precision conversion is required:

Shared layoutMeaning
ContiguousCompact HWC, including BF16 interleaved scalar bytes.
StridedDense HWC with optional channel padding, tight rows, scalar channel stride. Physical channels must be at least logical C; the profile sets the upper bound and alignment. Singleton outer strides equal the frame span.
MlaCBlockedCompiler transport with logical HWC tile extents; tile order C,H,W; within each tile, channel-block,H,W,16 lanes. Every logical channel slice is independently padded to 16. BF16 stripes store 16 low bytes followed by 16 high bytes. SSD admits nonmultiple-of-16 logical C tiles for INT8 and BF16. Other INT8 profiles, combined heads and EfficientDet require a multiple-of-16 C tile.

MlaCBlocked is deliberately distinct from MlaTiled, whose payload is pixel-major. MlaTiled and other stride patterns are rejected, not heuristically reinterpreted. Storage requirements include physical channel padding; the supplied view must cover that complete extent. Padding lanes do not become classes.

Dense physical channels are bounded by 1,024 for combined heads, SSD, EfficientDet and decoded CxCyWH, otherwise 256. Whole-head combined scans, EfficientDet scores and multi-anchor/objectness decoded scores require dense padding to 16. Compact dense storage is otherwise permitted. Packed C tiles have the same profile upper bound; partial spatial/channel tiles contribute to the required span. Do not substitute unary MlaTiled output without proving or adapting its different physical layout.

Packed score readers and delayed coordinate decoding are reused directly. No full-tensor detessellation, FP32 staging, or concatenation into a carrier is added.

Implementation notes​

Recipe-specific readers and immutable tables are prepared once. Score scans, lazy geometry decoding and candidate selection reuse context storage. Packed inputs are read directly rather than fully detessellated. Optional INT8 mask SDOT instructions are capability-gated; the widening implementation remains available. Exact work-reduction paths include prepared INT8 coordinate exponentials, combined-head objectness rejection, packed multi-anchor background bounds, mask padding initialization, and BF16 softmax tied maxima. These preserve the numerical and output contracts; they are not configurable approximation modes or performance guarantees.

Neither no-allocation execution nor SIMD dispatch guarantees constant latency: head sizes, surviving candidates, NMS overlap, K and mask crop/output sizes matter.

Tests and qualification​

The default A65 build includes correctness and latency executables covering the family recipes, tasks, dtypes and layouts. See build and test.

The independent correctness suite is in A65/boxdecode/tests/correctness.cpp (A65/boxdecode/tests/correctness.cpp). See build and test for commands and the distinction between host development checks, A65 measurements and baseline qualification.

For the arithmetic behind these contracts, see geometry and decoding and the RCNN proposal pipeline.

Changing the original image size​

Use check_image_geometry(plan, original_image_size) when the model and its output tensors are unchanged but the camera frame dimensions change. The returned immutable ImageGeometry retains that model plan and stores only the new image dimensions and bounded pose-affine coefficients. Its successful creation and copying do not allocate or rebuild readers, quantization tables, SSD priors or context scratch.

Call execute(plan, geometry, context, inputs, outputs) with a context created from that same plan (or a copy). The original execute overload still uses the image size supplied to check_inference. Geometry snapshots do not modify each other or the original plan; separate in-flight calls still require separate contexts and outputs. A geometry snapshot from an independently prepared plan is rejected, even when its tensor shapes happen to match.

Image dimensions retain the existing range of 1 through 1,048,576, and geometry preparation uses the same floating-point environment requirements as full preparation. Scaling, clipping, pose arithmetic, rounding and output padding are unchanged. Model input size, tensor layouts/shapes, quantization, thresholds, recipe, task and output capacity are not image-only updates: prepare a new plan when these change. This API does not reshape model outputs or allocate image storage.