include/brotensor/ops/conv.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | // brotensor ops/conv.h — Convolution: conv2d / conv3d / conv_transpose2d (+ int8 weight variants). | ||
| 4 | |||
| 5 | #include "../tensor.h" | ||
| 6 | #include <cstdint> | ||
| 7 | |||
| 8 | namespace brotensor { | ||
| 9 | |||
| 10 | |||
| 11 | // ─── Convolution: conv2d / conv3d / conv_transpose2d ─────────────────────── | ||
| 12 | // | ||
| 13 | // NCHW tensors are carried as flat (rows, cols) buffers; the (N,C,H,W) dims | ||
| 14 | // are passed as int args. Unless noted, these ops are dtype-dispatched on the | ||
| 15 | // primary input (FP32 or FP16) with FP32 internal accumulation, and resize + | ||
| 16 | // dtype-set their outputs to match. | ||
| 17 | |||
| 18 | // 2D convolution, NCHW. Dispatched on X.dtype (FP32/FP16); Wt, bias, Y share | ||
| 19 | // it. FP32 accumulation. | ||
| 20 | // X: (N, C_in*H*W). | ||
| 21 | // Wt: (C_out, (C_in/groups)*kH*kW) OIHW filter layout. | ||
| 22 | // bias: (C_out,1) or null. | ||
| 23 | // Y: (N, C_out*H_out*W_out), resized + dtype-set to match X. | ||
| 24 | // groups: divides C_in and C_out. Output channel c_out belongs to group | ||
| 25 | // g = c_out/(C_out/groups) and reads only input channels | ||
| 26 | // [g*(C_in/groups), (g+1)*(C_in/groups)). groups=1 is standard conv; | ||
| 27 | // groups==C_in==C_out is depthwise (Wt becomes (C_out, kH*kW)). | ||
| 28 | // H_out = (H + 2*pad_h - dil_h*(kH-1) - 1)/stride_h + 1 (W_out analogous). | ||
| 29 | void conv2d_forward(const Tensor& X, | ||
| 30 | const Tensor& Wt, | ||
| 31 | const Tensor* bias, | ||
| 32 | int N, int C_in, int H, int W, | ||
| 33 | int C_out, int kH, int kW, | ||
| 34 | int stride_h, int stride_w, | ||
| 35 | int pad_h, int pad_w, | ||
| 36 | int dil_h, int dil_w, | ||
| 37 | int groups, | ||
| 38 | Tensor& Y); | ||
| 39 | |||
| 40 | // Convenience overload: groups defaults to 1 (full convolution). | ||
| 41 | ✗ | inline void conv2d_forward(const Tensor& X, | |
| 42 | const Tensor& Wt, | ||
| 43 | const Tensor* bias, | ||
| 44 | int N, int C_in, int H, int W, | ||
| 45 | int C_out, int kH, int kW, | ||
| 46 | int stride_h, int stride_w, | ||
| 47 | int pad_h, int pad_w, | ||
| 48 | int dil_h, int dil_w, | ||
| 49 | Tensor& Y) { | ||
| 50 | ✗ | conv2d_forward(X, Wt, bias, N, C_in, H, W, C_out, kH, kW, | |
| 51 | ✗ | stride_h, stride_w, pad_h, pad_w, dil_h, dil_w, | |
| 52 | ✗ | /*groups=*/1, Y); | |
| 53 | ✗ | } | |
| 54 | |||
| 55 | |||
| 56 | // 2D convolution backward w.r.t. input. Dtype-dispatched (FP32/FP16); Wt and | ||
| 57 | // dY share dtype, dX matches. All conv hyperparams match the forward call. | ||
| 58 | // dX[n,c_in,i,j] = sum over kernel taps (kh,kw) and the group's output | ||
| 59 | // channels of dY[n,c_out,i_out,j_out]*Wt[c_out,c_in_local,kh,kw], where | ||
| 60 | // i_out = (i + pad_h - dil_h*kh)/stride_h (j_out analogous), | ||
| 61 | // counted only when divisible by stride and in [0,H_out)x[0,W_out). | ||
| 62 | // Wt: (C_out, (C_in/groups)*kH*kW) forward filter, OIHW. | ||
| 63 | // dY: (N, C_out*H_out*W_out) upstream gradient. | ||
| 64 | // dX: (N, C_in*H*W) overwritten, resized + dtype-set to dY. | ||
| 65 | void conv2d_backward_input(const Tensor& Wt, | ||
| 66 | const Tensor& dY, | ||
| 67 | int N, int C_in, int H, int W, | ||
| 68 | int C_out, int kH, int kW, | ||
| 69 | int stride_h, int stride_w, | ||
| 70 | int pad_h, int pad_w, | ||
| 71 | int dil_h, int dil_w, | ||
| 72 | int groups, | ||
| 73 | Tensor& dX); | ||
| 74 | |||
| 75 | // Convenience overload: groups defaults to 1. | ||
| 76 | ✗ | inline void conv2d_backward_input(const Tensor& Wt, | |
| 77 | const Tensor& dY, | ||
| 78 | int N, int C_in, int H, int W, | ||
| 79 | int C_out, int kH, int kW, | ||
| 80 | int stride_h, int stride_w, | ||
| 81 | int pad_h, int pad_w, | ||
| 82 | int dil_h, int dil_w, | ||
| 83 | Tensor& dX) { | ||
| 84 | ✗ | conv2d_backward_input(Wt, dY, N, C_in, H, W, C_out, kH, kW, | |
| 85 | ✗ | stride_h, stride_w, pad_h, pad_w, dil_h, dil_w, | |
| 86 | ✗ | /*groups=*/1, dX); | |
| 87 | ✗ | } | |
| 88 | |||
| 89 | |||
| 90 | // 2D convolution backward w.r.t. weights. Dtype-dispatched (FP32/FP16); X, dY, | ||
| 91 | // dWt share dtype. All conv hyperparams match the forward call. | ||
| 92 | // dWt[c_out,c_in_local,kh,kw] += sum over (n,i_out,j_out) of | ||
| 93 | // dY[n,c_out,i_out,j_out] * | ||
| 94 | // X[n,c_in, stride_h*i_out-pad_h+dil_h*kh, stride_w*j_out-pad_w+dil_w*kw] | ||
| 95 | // (OOB input reads treated as zero), with c_in = g*(C_in/groups)+c_in_local, | ||
| 96 | // g = c_out/(C_out/groups). | ||
| 97 | // X: (N, C_in*H*W) forward input. | ||
| 98 | // dY: (N, C_out*H_out*W_out) upstream gradient. | ||
| 99 | // dWt: (C_out, (C_in/groups)*kH*kW) accumulated — caller zeros. | ||
| 100 | void conv2d_backward_weight(const Tensor& X, | ||
| 101 | const Tensor& dY, | ||
| 102 | int N, int C_in, int H, int W, | ||
| 103 | int C_out, int kH, int kW, | ||
| 104 | int stride_h, int stride_w, | ||
| 105 | int pad_h, int pad_w, | ||
| 106 | int dil_h, int dil_w, | ||
| 107 | int groups, | ||
| 108 | Tensor& dWt); | ||
| 109 | |||
| 110 | // Convenience overload: groups defaults to 1. | ||
| 111 | ✗ | inline void conv2d_backward_weight(const Tensor& X, | |
| 112 | const Tensor& dY, | ||
| 113 | int N, int C_in, int H, int W, | ||
| 114 | int C_out, int kH, int kW, | ||
| 115 | int stride_h, int stride_w, | ||
| 116 | int pad_h, int pad_w, | ||
| 117 | int dil_h, int dil_w, | ||
| 118 | Tensor& dWt) { | ||
| 119 | ✗ | conv2d_backward_weight(X, dY, N, C_in, H, W, C_out, kH, kW, | |
| 120 | ✗ | stride_h, stride_w, pad_h, pad_w, dil_h, dil_w, | |
| 121 | ✗ | /*groups=*/1, dWt); | |
| 122 | ✗ | } | |
| 123 | |||
| 124 | |||
| 125 | // 2D convolution backward w.r.t. bias. Dtype-dispatched (FP32/FP16); dY and dB | ||
| 126 | // share dtype. | ||
| 127 | // dB[c_out] += sum over (n,i_out,j_out) of dY[n,c_out,i_out,j_out]. | ||
| 128 | // dY: (N, C_out*H_out*W_out) upstream gradient. | ||
| 129 | // dB: (C_out,1) accumulated — caller zeros. | ||
| 130 | void conv2d_backward_bias(const Tensor& dY, | ||
| 131 | int N, int C_out, int H_out, int W_out, | ||
| 132 | Tensor& dB); | ||
| 133 | |||
| 134 | |||
| 135 | // Modulated deformable 2D convolution forward — torchvision `deform_conv2d` | ||
| 136 | // (Deformable ConvNets v2), NCHW, forward/inference only. Each output pixel's | ||
| 137 | // kH×kW sampling grid is shifted per-tap by a learned `offset` field and | ||
| 138 | // (optionally) reweighted by a learned `mask` modulator; taps are bilinearly | ||
| 139 | // sampled from X with ZERO padding outside the input (torchvision convention). | ||
| 140 | // X: (N, C_in*H*W). | ||
| 141 | // offset: (N, deform_groups*2*kH*kW * H_out*W_out). Channel-major within a | ||
| 142 | // batch row: channel = grp*(2*kH*kW) + 2*(kh*kW+kw) [+1 for the col | ||
| 143 | // axis], then (H_out,W_out). Channel 2*(kh*kW+kw) is the ROW (y) | ||
| 144 | // offset, +1 the COL (x) offset — matches an offset_conv whose output | ||
| 145 | // channels are laid out (deform_groups, 2, kH, kW). | ||
| 146 | // mask: (N, deform_groups*kH*kW * H_out*W_out) or null. null == plain | ||
| 147 | // deformable conv (all modulators 1). Channel grp*(kH*kW)+(kh*kW+kw). | ||
| 148 | // Wt: (C_out, (C_in/groups)*kH*kW) OIHW (same layout as conv2d_forward). | ||
| 149 | // bias: (C_out,1) or null. | ||
| 150 | // Y: (N, C_out*H_out*W_out), resized + dtype-set to match X. | ||
| 151 | // groups divides C_in and C_out (regular conv grouping); deform_groups | ||
| 152 | // divides C_in (offset/mask grouping). H_out/W_out follow the conv2d formula. | ||
| 153 | // Dispatched FP32/FP16 on X.dtype (CPU is FP32-only); FP32 accumulation. No | ||
| 154 | // backward (an inference op — BiRefNet's ASPP-deformable decoder). | ||
| 155 | void deform_conv2d_forward(const Tensor& X, | ||
| 156 | const Tensor& offset, | ||
| 157 | const Tensor* mask, | ||
| 158 | const Tensor& Wt, | ||
| 159 | const Tensor* bias, | ||
| 160 | int N, int C_in, int H, int W, | ||
| 161 | int C_out, int kH, int kW, | ||
| 162 | int stride_h, int stride_w, | ||
| 163 | int pad_h, int pad_w, | ||
| 164 | int dil_h, int dil_w, | ||
| 165 | int groups, int deform_groups, | ||
| 166 | Tensor& Y); | ||
| 167 | |||
| 168 | |||
| 169 | // 3D convolution, NCTHW (forward only). Dispatched on X.dtype (FP32/FP16/BF16 | ||
| 170 | // on GPU; CPU is FP32-only). FP32 accumulation. | ||
| 171 | // X: (N, C_in*T*H*W). | ||
| 172 | // Wt: (C_out, (C_in/groups)*kT*kH*kW) OICTHW filter layout (grouped). | ||
| 173 | // bias: (C_out,1) or null. | ||
| 174 | // Y: (N, C_out*T_out*H_out*W_out), resized + dtype-set to match X. | ||
| 175 | // Per-axis stride/pad/dilation: (_t, _h, _w). `groups` divides C_in and C_out | ||
| 176 | // exactly as in conv2d (output channel c_out belongs to group | ||
| 177 | // c_out/(C_out/groups); reads input channels of that group only). | ||
| 178 | // T_out = (T + 2*pad_t - dil_t*(kT-1) - 1)/stride_t + 1 (H_out, W_out analogous). | ||
| 179 | // Y is OVERWRITTEN (the kernel stores acc directly), matching conv2d_forward. | ||
| 180 | void conv3d_forward(const Tensor& X, | ||
| 181 | const Tensor& Wt, | ||
| 182 | const Tensor* bias, | ||
| 183 | int N, int C_in, int T, int H, int W, | ||
| 184 | int C_out, int kT, int kH, int kW, | ||
| 185 | int stride_t, int stride_h, int stride_w, | ||
| 186 | int pad_t, int pad_h, int pad_w, | ||
| 187 | int dil_t, int dil_h, int dil_w, | ||
| 188 | int groups, | ||
| 189 | Tensor& Y); | ||
| 190 | |||
| 191 | // Convenience overload: groups defaults to 1 (full convolution). | ||
| 192 | inline void conv3d_forward(const Tensor& X, | ||
| 193 | const Tensor& Wt, | ||
| 194 | const Tensor* bias, | ||
| 195 | int N, int C_in, int T, int H, int W, | ||
| 196 | int C_out, int kT, int kH, int kW, | ||
| 197 | int stride_t, int stride_h, int stride_w, | ||
| 198 | int pad_t, int pad_h, int pad_w, | ||
| 199 | int dil_t, int dil_h, int dil_w, | ||
| 200 | Tensor& Y) { | ||
| 201 | conv3d_forward(X, Wt, bias, N, C_in, T, H, W, C_out, kT, kH, kW, | ||
| 202 | stride_t, stride_h, stride_w, pad_t, pad_h, pad_w, | ||
| 203 | dil_t, dil_h, dil_w, /*groups=*/1, Y); | ||
| 204 | } | ||
| 205 | |||
| 206 | |||
| 207 | // W8A16 3D convolution forward — the Qwen3-VL patch-embed variant of | ||
| 208 | // conv3d_forward. Same NCTHW / OICTHW layout, same per-axis stride/pad/dilation | ||
| 209 | // and groups semantics; X / bias FP16, W_int8 INT8 with per-output-row FP32 | ||
| 210 | // dequant scales (analogous to conv2d_int8w_fp16_forward). GPU-only — the CPU | ||
| 211 | // vtable slot is left null and the dispatcher throws "not implemented on CPU". | ||
| 212 | // X: (N, C_in*T*H*W) FP16. | ||
| 213 | // W_int8: (C_out, (C_in/groups)*kT*kH*kW) INT8. | ||
| 214 | // scales: (C_out, 1) FP32. | ||
| 215 | // bias: (C_out, 1) FP16 or null. | ||
| 216 | // Y: (N, C_out*T_out*H_out*W_out) FP16, resized + dtype-set as needed. | ||
| 217 | void conv3d_int8w_fp16_forward(const Tensor& X, | ||
| 218 | const Tensor& W_int8, | ||
| 219 | const Tensor& scales, | ||
| 220 | const Tensor* bias, | ||
| 221 | int N, int C_in, int T, int H, int W, | ||
| 222 | int C_out, int kT, int kH, int kW, | ||
| 223 | int stride_t, int stride_h, int stride_w, | ||
| 224 | int pad_t, int pad_h, int pad_w, | ||
| 225 | int dil_t, int dil_h, int dil_w, | ||
| 226 | int groups, | ||
| 227 | Tensor& Y); | ||
| 228 | |||
| 229 | // Convenience overload: groups defaults to 1. | ||
| 230 | inline void conv3d_int8w_fp16_forward(const Tensor& X, | ||
| 231 | const Tensor& W_int8, | ||
| 232 | const Tensor& scales, | ||
| 233 | const Tensor* bias, | ||
| 234 | int N, int C_in, int T, int H, int W, | ||
| 235 | int C_out, int kT, int kH, int kW, | ||
| 236 | int stride_t, int stride_h, int stride_w, | ||
| 237 | int pad_t, int pad_h, int pad_w, | ||
| 238 | int dil_t, int dil_h, int dil_w, | ||
| 239 | Tensor& Y) { | ||
| 240 | conv3d_int8w_fp16_forward(X, W_int8, scales, bias, | ||
| 241 | N, C_in, T, H, W, C_out, kT, kH, kW, | ||
| 242 | stride_t, stride_h, stride_w, | ||
| 243 | pad_t, pad_h, pad_w, | ||
| 244 | dil_t, dil_h, dil_w, /*groups=*/1, Y); | ||
| 245 | } | ||
| 246 | |||
| 247 | |||
| 248 | // 2D transposed convolution, NCHW. The 2D counterpart of conv_transpose1d, | ||
| 249 | // generalising independently across H and W. Used by SAM's mask decoder | ||
| 250 | // (4x upsampler), DPT depth heads, and any segmentation decoder that | ||
| 251 | // learns the upsample. Output spatial dims (torch ConvTranspose2d formula): | ||
| 252 | // H_out = (H - 1)*stride_h - 2*pad_h + dilation_h*(kH-1) + output_padding_h + 1 | ||
| 253 | // W_out = (W - 1)*stride_w - 2*pad_w + dilation_w*(kW-1) + output_padding_w + 1 | ||
| 254 | // Weight layout is input-channel-major (transposed-conv convention): | ||
| 255 | // Wt: (C_in, (C_out/groups)*kH*kW) with index | ||
| 256 | // (c_in*(Cg_out*kH*kW) + (oc_local*kH + kh)*kW + kw) | ||
| 257 | // bias may be null. groups must divide both C_in and C_out (default 1). | ||
| 258 | // output_padding must be < stride or < dilation on each axis (matches torch). | ||
| 259 | // X: (N, C_in*H*W). Y: (N, C_out*H_out*W_out), resized + dtype-set. | ||
| 260 | // CPU backend is FP32-only; the CUDA forward is dtype-dispatched on X | ||
| 261 | // (FP32/FP16/BF16 — Wt and bias must match, FP32 accumulation). The three | ||
| 262 | // backward ops are FP32-only on both backends. | ||
| 263 | void conv_transpose2d_forward(const Tensor& X, const Tensor& Wt, | ||
| 264 | const Tensor* bias, | ||
| 265 | int N, int C_in, int H, int W, | ||
| 266 | int C_out, int kH, int kW, | ||
| 267 | int stride_h, int stride_w, | ||
| 268 | int pad_h, int pad_w, | ||
| 269 | int output_padding_h, int output_padding_w, | ||
| 270 | int dil_h, int dil_w, int groups, | ||
| 271 | Tensor& Y); | ||
| 272 | |||
| 273 | |||
| 274 | // Backward to the input. dX OVERWRITTEN — the adjoint of the forward | ||
| 275 | // scatter is a plain gather conv (cross-correlation in disguise). | ||
| 276 | // Wt: (C_in, (C_out/groups)*kH*kW). dY: (N, C_out*H_out*W_out). | ||
| 277 | // dX: (N, C_in*H*W), resized + dtype-set. | ||
| 278 | void conv_transpose2d_backward_input(const Tensor& Wt, const Tensor& dY, | ||
| 279 | int N, int C_in, int H, int W, | ||
| 280 | int C_out, int kH, int kW, | ||
| 281 | int stride_h, int stride_w, | ||
| 282 | int pad_h, int pad_w, | ||
| 283 | int output_padding_h, int output_padding_w, | ||
| 284 | int dil_h, int dil_w, int groups, | ||
| 285 | Tensor& dX); | ||
| 286 | |||
| 287 | |||
| 288 | // Backward to the weights. dWt ACCUMULATES (+=) — caller zeros it first | ||
| 289 | // (matches the conv2d contract). | ||
| 290 | // X: (N, C_in*H*W). dY: (N, C_out*H_out*W_out). | ||
| 291 | // dWt: (C_in, (C_out/groups)*kH*kW), pre-zeroed by caller. | ||
| 292 | void conv_transpose2d_backward_weight(const Tensor& X, const Tensor& dY, | ||
| 293 | int N, int C_in, int H, int W, | ||
| 294 | int C_out, int kH, int kW, | ||
| 295 | int stride_h, int stride_w, | ||
| 296 | int pad_h, int pad_w, | ||
| 297 | int output_padding_h, int output_padding_w, | ||
| 298 | int dil_h, int dil_w, int groups, | ||
| 299 | Tensor& dWt); | ||
| 300 | |||
| 301 | |||
| 302 | // Backward to the bias. dB ACCUMULATES (+=) — caller zeros first. | ||
| 303 | // dY: (N, C_out*H_out*W_out). dB: (C_out, 1). | ||
| 304 | void conv_transpose2d_backward_bias(const Tensor& dY, int N, int C_out, | ||
| 305 | int H_out, int W_out, Tensor& dB); | ||
| 306 | |||
| 307 | |||
| 308 | // W8A16 conv2d forward. Mirrors conv2d_forward; only the weight dtype differs. | ||
| 309 | // W_int8: (C_out, C_in/groups*kH*kW) INT8 OIHW, quantised per output channel. | ||
| 310 | // scales: (C_out,1) FP32 per-output-channel dequant scales. | ||
| 311 | // bias: (C_out,1) FP16 or null. X, Y: FP16, layout as conv2d_forward. | ||
| 312 | void conv2d_int8w_fp16_forward(const Tensor& X, | ||
| 313 | const Tensor& W_int8, | ||
| 314 | const Tensor& scales, | ||
| 315 | const Tensor* bias, | ||
| 316 | int N, int C_in, int H, int W, | ||
| 317 | int C_out, int kH, int kW, | ||
| 318 | int stride_h, int stride_w, | ||
| 319 | int pad_h, int pad_w, | ||
| 320 | int dil_h, int dil_w, int groups, | ||
| 321 | Tensor& Y); | ||
| 322 | |||
| 323 | } // namespace brotensor | ||
| 324 |