1---2name: cuda-kernel-craft3description: Use when writing, optimizing, or reviewing CUDA kernel code itself (the __global__ functions) — block/grid sizing, memory coalescing, divergence, shared memory, precision, and Jetson Orin (SM87) specifics. Complements rust-cuda-patterns (which covers the Rust launch side).4---56# CUDA Kernel Craft (Jetson Orin SM87)78## Orin hardware facts that shape kernel design910- Ampere-class iGPU, **unified memory**: CPU and GPU share LPDDR5. There is11 no PCIe — "H2D copies" are memory-to-memory, but bandwidth (~200 GB/s) is12 shared with the CPU, decoder, and VIC. Image-processing kernels here are13 almost always **bandwidth-bound, not compute-bound** — optimize bytes14 moved, not FLOPs.15- Warp size 32, max 1024 threads/block. FP16 arithmetic is double-rate, but16 our kernels read FP32 TRT outputs, so FP32 throughout is correct.17- Texture/`__ldg` path is effective on Orin for read-only data with 2D18 locality (see `xfeat_score_nms`'s 25-neighbour window).1920## Repo conventions for block/grid sizing2122- **2D image kernels**: `block = (32, 8)` — 256 threads, x-dim = warp size so23 consecutive threads read consecutive addresses (coalesced). Grid covers the24 image with ceil-div: `((w + 31)/32, (h + 7)/8)`. Used by every image kernel25 in the repo; don't invent new shapes without a measured reason.26- **Per-item kernels** (one item per block): `grid = (K,1,1)`,27 `block = (64,1,1)` matching the 64-D descriptor — each thread owns one28 channel (see `xfeat_sample_descs`, `xfeat_l2_norm`).29- Always bounds-check first: `if (x >= W || y >= H) return;` — grids overshoot.3031## Memory access3233- **Coalescing rule**: thread `x` and thread `x+1` should touch addresses 434 bytes apart. For CHW tensors, index `[c][y][x]` with x innermost — looping35 over channels inside a thread is fine, striding x across threads is not.36- Mark read-only pointer params `const float* __restrict__` and read hot37 reused data with `__ldg(&p[i])` — lets the compiler use the read-only38 cache. All existing kernels do this.39- Avoid read-modify-write to global memory in loops; accumulate in registers,40 write once at the end (see `xfeat_l2_norm` pattern: sum in register,41 one `rsqrtf`, then scale-and-store).42- Shared memory: justified only when a block reuses the same global data many43 times (e.g. tiled windows). The 5×5 NMS deliberately uses `__ldg` instead —44 simpler and the read-only cache already captures the overlap. Measure45 before adding `__shared__` complexity.4647## Control flow & precision4849- Early-`return` divergence is cheap when spatially coherent (e.g. most50 pixels fail the NMS threshold together). Avoid divergence that differs51 per-lane within a warp in hot loops.52- Branchless `min/max/clamp` (`fminf`, `fmaxf`) over `if` for range clamps.53- Use float literals (`0.0f`, `114.0f/255.0f`) — a bare `0.5` is double and54 forces FP64 ops, which are 1/32 rate on Orin.55- `rsqrtf`, `__fdividef` are fine for normalization (descriptor precision56 tolerates fast-math); do NOT fast-math coordinate computations that feed57 bilinear sampling — sub-pixel keypoint accuracy matters.58- Bilinear sampling convention is **align_corners=False**59 (`src = (dst + 0.5) * scale - 0.5`) to match PyTorch `grid_sample` — any60 new resampling kernel must use the same convention or descriptors shift.6162## Correctness checklist for new kernels63641. Bounds check at the top.652. No assumption that W/H are multiples of block dims (ceil-div grid).663. Output fully written for every in-bounds thread (or buffer pre-zeroed67 with `alloc_zeros`) — `stream.alloc` is uninitialized.684. No inter-block dependencies — there is no global sync inside a kernel.69 If a reduction needs all blocks' results, split into two kernels or do70 the final step on CPU (the top-K does exactly this).715. Test against a scalar CPU reference on a small synthetic input where72 the expected output is hand-computable (edge pixels included).7374## Optimizing: measure first7576- `gpu_ms` in `PipelineTiming` is per-frame whole-pipeline GPU time; to77 isolate one kernel, bracket it with `stream.record_event(None)` pairs.78- For deep dives: `sudo /opt/nvidia/nsight-compute/ncu --set basic <binary>`79 (kernel-level) or `nsys profile` (timeline). Run at MAXN_SUPER.80- A kernel at < 0.2ms on 1280×736 is in the noise next to the ~10ms81 backbone — don't optimize it; fuse it or leave it.82- GPU top-K without a CPU round trip: histogram-cutoff (bin scores → scan83 for the K-th threshold → atomic-gather above it). Approximate at the84 boundary bin but avoids the mid-frame device→host→device sort. See85 xfeat_topk_* in vrt-xfeat. Output is atomic-append order, not sorted.86- Fusing beats micro-tuning here: `xfeat_score_nms` fuses NMS + score87 multiply into one pass to halve traffic. Look for fusion (one read, one88 write) before tweaking block sizes.