Native Histogram boundary
Contract
Histogram accepts one positive, contiguous UInt8 grayscale tensor with rank
3–6 and trailing dimensions H,W,1. Every preceding coordinate identifies an
independent image. Its explicit output is contiguous Int32 with shape
[outer...,256]; an unbatched [H,W,1] input produces [256].
There is no implicit channel conversion, rank-two shorthand, stride support, mask, weighted histogram, spatial grid or A65 fallback.
ops::histogram::check_inference() validates the complete request and derives
physical storage requirements. Dimensions must be positive and each image must
contain at most INT32_MAX pixels, preventing signed bin-count overflow.
EV74 admission also rejects spans exceeding its 32-bit address representation.
A six-axis shape is supported; shapes are not automatically padded to rank six.
Parameters
The typed ops::histogram::Params structure has no fields.
The number of bins is fixed at 256. Worker count, local DMA chunk size and
scratch partitioning are implementation details, not caller-selected flags.
Numerical behavior
For each image and each integer value v from 0 through 255:
output[v] = number of input pixels exactly equal to v
Counts are exact Int32 integers; there is no scaling, saturation, normalization
or floating-point arithmetic. Every output bin is overwritten on every call,
including bins whose count is zero. Summing the 256 bins gives H*W.
C++ call
Use simaai/kernels/ops/histogram.h for requests and
simaai/kernels/ev74/histogram.h for device encoding. Link
SimaKernels::histogram for typed Runner calls or direct codec use.
Place the includes and namespace aliases at file scope; the remaining statements
belong in a simaai::kernels::Status-returning caller function. It checks metadata and
encodes a relocatable descriptor, not EV74 computation. Runtime must map actual
buffers, bind addresses and retain inputs, outputs and any workspace until
confirmed device completion.
#include <array>
#include <simaai/kernels/ev74/histogram.h>
using namespace simaai::kernels;
const std::array inputs{TensorSpec{Shape{2, 7, 13, 1}, DType::UInt8}};
const std::array outputs{TensorSpec{Shape{2, 256}, DType::Int32}};
const auto checked = ops::histogram::check_inference(
{inputs, outputs, {}});
if (const auto *e = std::get_if<Error>(&checked)) return *e;
const auto &plan = std::get<ops::histogram::Plan>(checked);
const auto invocation = ev74::histogram::encode_relocatable(plan);
if (const auto *e = std::get_if<Error>(&invocation)) return *e;
// Encoding leaves device addresses unbound; no device work has run.
return {};
See the host contract tests (EV74/histogram/tests/correctness.cpp) and
device fixture (EV74/histogram/tests/runtime_e2e.cpp) for binding and execution coverage.
The Runtime path prepares the typed request, verifies a matching native
firmware capability and submits explicit input, output and workspace views.
Executor::submit(prepared, inputs, outputs, workspace) is asynchronous;
completion, not submission return, ends buffer use. The
device fixture (EV74/histogram/tests/runtime_e2e.cpp) demonstrates this
path using installed public packages.
Memory and lifecycle
For B images, input needs B*H*W bytes and byte alignment. Output needs
B*256*4 bytes and 16-byte alignment. Prepared workspace requirements reserve
B*4*256*4 bytes with 16-byte alignment for up to four per-worker partials.
Input, output and workspace spans must not overlap.
Workspace is not a semantic output. For multiworker execution, only active-worker partials are initialized; unused workspace bytes have no value contract. Bytes beyond admitted output or workspace spans are not written. Runtime/callers provide and retain distinct workspace for each in-flight invocation; sharing immutable plans does not make shared writable buffers safe. Preparation does not allocate device storage.
The host codec checks region capacities, alignment, address ranges and overlap. Device decoding independently validates wire fields, address arithmetic and pairwise overlap. A device address is not proof of accessible storage: allocation, mapping, coherency and confirmed retirement remain Runtime responsibilities. Native computation allocates no heap storage after kernel handles are initialized. A failed operation may have modified workspace or output; rollback is not promised.
Implementation notes
The native entry uses up to four available workers, capped by useful SIMD vectors. Counting partitions contiguous pixel intervals on complete-vector boundaries, rather than row boundaries; the last interval owns the exact tail. This balances wide/short and tall/narrow images without changing bin counts. Larger intervals use 16-KiB double-buffered DMA and sixteen private lane histograms, preventing same-instruction scatter collisions even on constant images. Each worker reduces its lanes to one 256-bin partial. A single-worker reduction kernel loads the contiguous partials in one bounded DMA and sums them into output. Blocking dispatch retires partial writes before reduction.
An interval of at most sixteen pixels counts directly into a cleared 256-bin panel instead of initializing sixteen lane banks. Batches of such images use a separate bounded worker that owns complete per-image outputs; they require no partial workspace writes or reduction dispatch. Likewise, a single counting worker writes its complete result directly. Workspace remains required by the shared binding contract but is not initialized by these direct-output paths.
The regular counting worker's local arrays occupy approximately 49 KiB; the tiny batch worker has its own smaller bounded storage. Source structure alone does not establish bandwidth or latency. Native dispatcher calls remain serialized. Fixed-width wire fields and the C++/OpenCL control layout have size, alignment and field-offset assertions. Platform firmware must explicitly compose the native entry; installing the host codec or kernel archive does not install a device route.
Tests and qualification
EV74/histogram/tests/correctness.cpp covers admission, codec binding, malformed
wire descriptors, dispatcher failures, cached handles, ranks 3–6, outer batches,
constant/all-value/random inputs and vector/DMA tails. When Clang is available,
expressions.cpp interprets the actual OpenCL source against an independent
scalar bin-count oracle, with immediate and deferred checked DMA, dirty reuse
and guards. These are host models, not proof of EV instructions or physical DMA.
The standalone EV74/histogram/tests/e2e CMake project consumes installed
SimaRuntime. Its device executable runs exact correctness and submit-to-completion
latency by default; --correctness-only, --latency-only and --list-cases
select explicit modes. Measurements include chronological samples, process CPU
cost and workload storage sizes. Large-to-small reuse preserves bytes outside
the current admitted spans. No device job is automatically registered in CTest.
Host model checks and a private target archive compile do not establish device correctness, fresh latency qualification or whole-firmware neutrality. Those remain separate acceptance steps, including neighboring operation regressions and a reviewed same-operation baseline. No numerical tolerance is permitted.