-
Notifications
You must be signed in to change notification settings - Fork 11
Architecture: Kernel Wrappers
This page documents the kernel wrapper chain in include/core/wrappers/: the non-owning accessor types that bridge containers and kernels, how they compose, and how operators build them.
A kernel wrapper is a small, trivially copyable, non-owning accessor over container memory. Operators build a wrapper on the host, pass it by value into a kernel, and the kernel reads/writes pixels through it. Wrappers carry pointers and stride math — never ownership — so they free nothing.
Wrappers compose. A base accessor over a container is decorated by a border-handling wrapper, which is decorated by an interpolation wrapper. Each layer adds one responsibility and forwards the rest:
base accessor →
BorderWrapper→InterpolationWrapper→ consumed by kernel
The composition works because every layer satisfies the same informal wrapper concept:
| Member | Meaning |
|---|---|
ValueType |
Pixel type the wrapper reads/writes (e.g. uchar3, float4) |
at(n, h, w, c) |
Access channel c of pixel (h, w) in image n
|
width(n) / height(n)
|
Per-image dimensions; n defaults to 0
|
batches() |
Number of images |
channels() |
Channels per pixel |
Because the border and interpolation decorators are templated on the wrapper they wrap (not on a fixed base type), the same border/interpolation math serves both uniform tensors and variable-shape image batches with no duplication.
Each container has exactly one base accessor. Both expose the wrapper concept; they differ in how they resolve at, width, and height.
include/core/wrappers/tensor_wrapper.hpp — bounds-unsafe accessor over a Tensor with NHWC, NCHW, HWC, or CHW layout.
-
Template:
T— pixel type.BaseType = detail::BaseType<T>is the scalar element. -
Construction: from a
Tensor(reads layout, normalizes to{n, h, w, c}shape/stride; HWC/CHW get batch shape1, batch stride0); also from astd::vector<BaseType>&or avoid*with explicitbatchSize/width/height. -
at(n, h, w, c)computes a byte offset from per-dimension strides and reinterprets toT&. No bounds checking. -
width(n)/height(n)ignorenand return the single shared dimension. The unusednparameter exists only so the signature matchesImageBatchVarShapeWrapper, letting both satisfy the same concept (include/core/wrappers/tensor_wrapper.hpp:149). - Other layouts throw
NOT_IMPLEMENTEDat construction.
Used for both input and output. Output tensors use it directly (no border/interpolation decoration).
include/core/wrappers/image_batch_var_shape_wrapper.hpp — accessor over an ImageBatchVarShape's exported descriptor table.
-
Template:
T— single pixel type (uchar1,uchar3,uchar4,float1,float4, …). Channel count derives fromdetail::NumElements<T>. -
Construction: from an
ImageBatchVarShapeDataStridedsnapshot (input.exportData(stream)). Copies out the descriptor-table pointer and image count. -
at(n, h, w, c)indexes descriptorn, then applies single-plane interleaved (NHWC-style) stride math:basePtr + h*rowStride + w*sizeof(T) + c*sizeof(BaseType). -
width(n)/height(n)read per-image dimensions from descriptorn. This is the difference that makes variable shapes work. -
Residency follows the snapshot. A GPU (
...Hip) snapshot yields a device pointer (use in device kernels); a CPU (...Host) snapshot yields a host pointer (use in host code). Everyat/width/heightdereferences it, so the caller must run the wrapper on the matching side. - Single-plane interleaved only;
ImageBatchVarShaperejects multi-plane images atpushBack.
include/core/wrappers/generic_tensor_wrapper.hpp — rank-agnostic accessor for tensors that are not images.
-
Template:
T— element type. -
Construction: from a
Tensor(copiesshape/strides/rank), or from rawvoid*+ shape + strides + rank. -
at(idx...)takes a variadic coordinate list and dots it againststrides. - Does not expose
width/height/batches/channels, so it does not satisfy the image wrapper concept and does not compose withBorderWrapper/InterpolationWrapper. It is for side-channel data such as histogram bins or threshold tables. Seeop_histogram.cppandthresholding_host.hpp.
include/core/wrappers/border_wrapper.hpp — adds out-of-bounds handling on top of a base accessor.
template <eBorderType BorderType, typename W>
class BorderWrapper;-
Templates:
BorderType— the border policy (compile-time);W— the wrapped base accessor type.ValueTypeis recovered fromW::ValueType; the border mode is exposed as thekBorderTypeconstant andWasWrapperType. -
Construction:
BorderWrapper(W image_wrapper, ValueType border_value).border_valueis the fallback used only byBORDER_TYPE_CONSTANT. -
at(n, h, w, c)returns the in-bounds value when(w, h)lies insidewidth(n) × height(n); otherwise it remaps the coordinate per the border policy (or returns the constant) and reads throughW.
In-bounds reads take an early-return branch to skip the remap math for the common case (include/core/wrappers/border_wrapper.hpp:87).
From include/operator_types.h (eBorderType):
| Value | Behavior |
|---|---|
BORDER_TYPE_CONSTANT |
Returns border_value for out-of-bounds coordinates |
BORDER_TYPE_REPLICATE |
Clamps the coordinate to the last valid row/column |
BORDER_TYPE_REFLECT |
Reflects across the edge, including the boundary pixel |
BORDER_TYPE_REFLECT101 |
Reflects across the edge, excluding the boundary pixel |
BORDER_TYPE_WRAP |
Wraps the coordinate to the opposite edge |
Each policy is a if constexpr branch selected at compile time, so only the chosen one is instantiated.
template <eBorderType B, typename W>
auto MakeBorderWrapper(W wrap, typename W::ValueType borderValue);Deduces W from the argument so callers spell only the border-mode policy.
include/core/wrappers/interpolation_wrapper.hpp — adds sub-pixel sampling on top of a BorderWrapper. Read-only; do not use for output tensors.
template <eInterpolationType I, typename BW>
class InterpolationWrapper;-
Templates:
I— interpolation policy (compile-time);BW— theBorderWrappertype.ValueTypecomes fromBW::ValueType;BWis exposed asBorderTypeandIaskInterpolationType. Templating directly on theBorderWrappermeans the border mode and base wrapper are spelled once. -
Construction:
InterpolationWrapper(BW borderWrapper). -
at(n, h, w, c)takes floath/wand samples. Reads go through the innerBorderWrapper::at, so out-of-bounds taps are border-handled automatically.
From include/operator_types.h (eInterpolationType):
| Value | Sampling |
|---|---|
INTERP_TYPE_NEAREST |
Rounds to the nearest pixel (lroundf) |
INTERP_TYPE_LINEAR |
Bilinear over the 2×2 neighborhood |
INTERP_TYPE_CUBIC |
Catmull-Rom bicubic over the 4×4 neighborhood |
Linear and cubic promote each tap to a float work type (detail::MakeType<float, NumElements<ValueType>>) via detail::RangeCast, accumulate, then range-cast back to ValueType. Cubic weights come from CalBicubicWeights.
template <eInterpolationType I, typename BW>
auto MakeInterpolationWrapper(BW borderWrap);Deduces BW (and its border mode + base wrapper) from the argument; callers spell only the interpolation policy.
src/op_resize.cpp builds the chain once and reuses it for both the uniform-tensor and variable-shape input paths. The base wrapper is built by the caller; everything downstream is identical (src/op_resize.cpp:42).
template <typename T, eInterpolationType I, typename SrcBaseWrapper>
void dispatch_resize_launch(hipStream_t stream, SrcBaseWrapper srcBase,
const Tensor& output, eDeviceType device) {
TensorWrapper<T> outputWrapper(output);
// Resize clamps at the image border (REPLICATE).
auto inputWrapper = MakeInterpolationWrapper<I>(
MakeBorderWrapper<eBorderType::BORDER_TYPE_REPLICATE>(srcBase, T{}));
switch (device) {
case eDeviceType::GPU: {
dim3 block(64, 16);
dim3 grid(/* over output width/height */, outputWrapper.batches());
Kernels::Device::resize<<<grid, block, 0, stream>>>(inputWrapper, outputWrapper);
break;
}
case eDeviceType::CPU:
Kernels::Host::resize(inputWrapper, outputWrapper);
break;
}
}The two entry points differ only in the base wrapper they construct (src/op_resize.cpp:65):
// Uniform tensor input
dispatch_resize_launch<T, I>(stream, TensorWrapper<T>(input), output, device);
// Variable-shape batch input
auto data = input.exportData(stream);
dispatch_resize_launch<T, I>(stream, ImageBatchVarShapeWrapper<T>(data), output, device);The kernel is wrapper-agnostic — it names no concrete wrapper type, only the concept (include/kernels/device/resize_device.hpp):
template <typename SrcWrapper, typename DstWrapper>
__global__ void resize(SrcWrapper input, DstWrapper output) {
const int x = blockIdx.x * blockDim.x + threadIdx.x;
const int y = blockIdx.y * blockDim.y + threadIdx.y;
const int batch = blockIdx.z;
if (x >= output.width() || y >= output.height()) return;
float scaleX = input.width(batch) / static_cast<float>(output.width());
float scaleY = input.height(batch) / static_cast<float>(output.height());
float srcX = fmaf(x + 0.5f, scaleX, -0.5f);
float srcY = fmaf(y + 0.5f, scaleY, -0.5f);
output.at(batch, y, x, 0) = input.at(batch, srcY, srcX, 0);
}input.width(batch) resolves to the shared width for TensorWrapper and to image batch's own width for ImageBatchVarShapeWrapper — the per-image scale falls out for free. The host kernel (include/kernels/host/resize_host.hpp) is the same logic under an OpenMP loop.
| Type | Header | Role |
|---|---|---|
TensorWrapper<T> |
include/core/wrappers/tensor_wrapper.hpp |
Base accessor for NHWC/NCHW/HWC/CHW tensors |
ImageBatchVarShapeWrapper<T> |
include/core/wrappers/image_batch_var_shape_wrapper.hpp |
Base accessor for variable-shape image batches |
GenericTensorWrapper<T> |
include/core/wrappers/generic_tensor_wrapper.hpp |
Rank-agnostic accessor for non-image data |
BorderWrapper<BorderType, W> |
include/core/wrappers/border_wrapper.hpp |
Border-handling decorator + MakeBorderWrapper
|
InterpolationWrapper<I, BW> |
include/core/wrappers/interpolation_wrapper.hpp |
Interpolation decorator + MakeInterpolationWrapper
|
eBorderType, eInterpolationType
|
include/operator_types.h |
Border and interpolation policy enums |
- Home
- Contributing
-
Architecture
- Overview
- Containers
- Kernel Wrappers
- Operator Flow