fovi.arch.knn_pool_cuda
Native CUDA (CuPy NVRTC) kernels for KNNPoolingLayer.
The baseline layer (fovi/arch/knn.py) materializes a [B, C, K, Nout] NaN-padded
gather (194 MiB at alexp0 B=512 fp16) and reduces it with NaN-aware torch ops; the max
path additionally performs a boolean-mask in-place fill whose nonzero triggers a host
sync every call, and its backward runs a max-scatter + index_put backward + fp16
scatter_add_ chain (~3x the forward). This module replaces both directions with one
kernel each:
Forward (
knn_pool_forward): fused gather + reduce. One CTA stagesRSconsecutive(b, c)rows ofx(rows are contiguous in global memory, so the staging copy is a single flat coalesced range) plus the persistent[K, Nout]pad-token index table into shared memory, then reduces every(row, n)in registers. TheB*C*K*Noutintermediate is never materialized and no NaN sentinel is needed (pad entries are detected byindex == Nin).maxemits auint8argmax slot per(b, c, n)for the backward, using the strict>/ ascending-kupdate whose tie rule (first occurrence of the maximum) matchestorch.maxon CUDA (verified empirically; the baseline backward routes ties identically).avgaccumulates valid neighbors in fp32 and divides by the precomputed valid count with an IEEE division, matchingnanmean’s sum-then-divide bit-for-bit (0/0 = NaN reproduces the empty-slice NaN for all-pad neighborhoods).Backward (
knn_pool_backward): deterministic, atomic-free reverse-CSR gather. For each input nodemthe persistent CSR (built once per layer) lists the packed(k, n)pairs referencing it; everydx[b, c, m]is accumulated in fp32 registers and written exactly once (so a NaN-prefill canary works for gradients too, unlike accumulate-into-zeros designs).maxaddsgy[b, c, n]where the stored argmax equalsk;avgaddsgy[b, c, n] / count[n]. This removes the baseline’s fp16 scatter-add gradient noise entirely. Pooling is parameter-free and channel-wise, so there is no weight gradient and noCin*Kcontraction anywhere.
Host-path doctrine from the conv kernels applies (sub-ms calls are host-bound): kernel
arguments are raw data_ptr() values, launches go through the conv module’s cached
ExternalStream path, and all derived index structures are built once per layer and
cached. Unlike the conv Function, the pooling backward needs neither x nor any
re-layout of grad_y — only the uint8 argmax map is saved for training (max mode),
an activation-memory win over the baseline’s saved gather intermediates.
NVRTC note: torch must be imported before CuPy (see fovi.arch.knn_cuda); importing
that module first guarantees the ordering and provides the shared launch machinery.
- class fovi.arch.knn_pool_cuda.PoolMeta(nin: int, nout: int, k: int, indices: Tensor, indices_i64: Tensor, count: Tensor, inv_count: Tensor, rev_rowptr: Tensor, rev_kn: Tensor, device: device)[source]
Bases:
objectDerived, device-resident index tables for one pooling layer.
- fovi.arch.knn_pool_cuda.pool_meta_from_indices(indices, nin, device=None)[source]
Build
PoolMetafrom a[K, Nout]pad-token index table.indicesfollows the layer convention: entries>= nin(the layer uses exactlynin) are padding. The reverse CSR drops padding entries, packsj = (k << 24) | nand orders entries by (input node, then flattenedk*Nout + n) via a stable sort, so the backward’s fp32 register accumulation is deterministic.
- fovi.arch.knn_pool_cuda.ensure_pool_metadata(layer, device)[source]
Build (or fetch cached)
PoolMetafor aKNNPoolingLayer.Cached on
layer._knn_pool_cuda_meta(never entersstate_dict); the index table is fixed at layer construction, so the cache is keyed by device only.
- fovi.arch.knn_pool_cuda.pool_forward(meta, x, mode, rs=None, need_aux=True)[source]
Fused KNN pooling forward.
- Parameters:
meta –
PoolMetafor the layer’s index table.x –
[B, C, Nin]CUDA tensor, float16 / bfloat16 / float32.mode –
"max"or"avg".rs – optional rows-per-CTA override (default: heuristic).
need_aux – emit the argmax map (max mode only; skip at inference to save traffic).
- Returns:
yis[B, C, Nout]inx.dtype;auxis theuint8argmax map (max mode withneed_aux) orNone.- Return type:
(y, aux)
- fovi.arch.knn_pool_cuda.pool_backward(meta, grad_y, aux, mode, rs=None)[source]
Deterministic reverse-CSR KNN pooling backward.
- Parameters:
meta –
PoolMeta(must be the forward’s meta).grad_y –
[B, C, Nout]CUDA tensor, float16 / bfloat16 / float32.aux – the forward’s
uint8argmax map (required formax; ignored foravg).mode –
"max"or"avg".rs – optional rows-per-CTA override.
- Returns:
[B, C, Nin]gradient ingrad_y.dtype(fp32 register accumulation, one final rounding — strictly less gradient noise than the baseline’s fp16scatter_add_chain).
- class fovi.arch.knn_pool_cuda.KNNPoolFunction(*args, **kwargs)[source]
Bases:
FunctionAutograd wrapper: fused forward + deterministic rev-CSR backward.
Pooling is parameter-free and not autocast-eligible (the baseline runs in the input dtype under AMP, consuming the preceding conv’s half-precision output), so no dtype cast happens here: compute dtype ==
x.dtype. Only theuint8argmax map is saved for max mode — neitherxnor any gather intermediate is retained.- static forward(ctx, x, meta, mode, need_aux)[source]
Define the forward of the custom autograd Function.
This function is to be overridden by all subclasses. There are two ways to define forward:
Usage 1 (Combined forward and ctx):
@staticmethod def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any: pass
It must accept a context ctx as the first argument, followed by any number of arguments (tensors or other types).
See Combined or separate forward() and setup_context() for more details
Usage 2 (Separate forward and ctx):
@staticmethod def forward(*args: Any, **kwargs: Any) -> Any: pass @staticmethod def setup_context(ctx: Any, inputs: Tuple[Any, ...], output: Any) -> None: pass
The forward no longer accepts a ctx argument.
Instead, you must also override the
torch.autograd.Function.setup_context()staticmethod to handle setting up thectxobject.outputis the output of the forward,inputsare a Tuple of inputs to the forward.See Extending torch.autograd for more details
The context can be used to store arbitrary data that can be then retrieved during the backward pass. Tensors should not be stored directly on ctx (though this is not currently enforced for backward compatibility). Instead, tensors should be saved either with
ctx.save_for_backward()if they are intended to be used inbackward(equivalently,vjp) orctx.save_for_forward()if they are intended to be used for injvp.
- static backward(ctx, grad_y)[source]
Define a formula for differentiating the operation with backward mode automatic differentiation.
This function is to be overridden by all subclasses. (Defining this function is equivalent to defining the
vjpfunction.)It must accept a context
ctxas the first argument, followed by as many outputs as theforward()returned (None will be passed in for non tensor outputs of the forward function), and it should return as many tensors, as there were inputs toforward(). Each argument is the gradient w.r.t the given output, and each returned value should be the gradient w.r.t. the corresponding input. If an input is not a Tensor or is a Tensor not requiring grads, you can just pass None as a gradient for that input.The context can be used to retrieve tensors saved during the forward pass. It also has an attribute
ctx.needs_input_gradas a tuple of booleans representing whether each input needs gradient. E.g.,backward()will havectx.needs_input_grad[0] = Trueif the first input toforward()needs gradient computed w.r.t. the output.
- fovi.arch.knn_pool_cuda.pool_function(x, indices, mode)[source]
Benchmark-harness-compatible entry point:
(x, [K, Nout] indices, mode) -> ywith full autograd support (meta cached by index-table identity).
- fovi.arch.knn_pool_cuda.optimized_pool_forward(layer, x)[source]
Layer-level dispatch mirroring
fovi.arch.knn_optimization.optimized_forward().Returns the optimized result, or
Nonewhen the caller should fall back to the baseline (unsupported mode/dtype/device, shape outside the shared-memory envelope). Reads onlylayer.mode,layer.knn_indices_pad_tokenandlayer.knn_pad_token_val(in_coords.shape[0]fallback); caches metadata onlayer._knn_pool_cuda_meta.