src/cpu/modulated_conv2d.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU modulated_conv2d (StyleGAN3) ─────────────────────────────────────── | ||
| 2 | // | ||
| 3 | // The StyleGAN synthesis-layer core: per-sample style modulation of the conv | ||
| 4 | // weights, optional demodulation, then a standard stride-1 conv per sample. | ||
| 5 | // FP32 reference mirroring NVlabs `modulated_conv2d` (the pure-PyTorch path). | ||
| 6 | // | ||
| 7 | // Realized by looping the batch and reusing the validated CPU conv2d kernels | ||
| 8 | // (groups=1) on a per-sample weight — only the weight construction + demod is | ||
| 9 | // new. Layouts: X (N,C_in*H*W) NCHW; W (C_out, C_in*kH*kW) OIHW; s (N,C_in). | ||
| 10 | // | ||
| 11 | // w'[o,i,kh,kw] = W[o,i,kh,kw] * s[n,i] | ||
| 12 | // dcoef[n,o] = demodulate ? rsqrt(Σ_{i,kh,kw} w'^2 + eps) : 1 | ||
| 13 | // w'' = w' * dcoef[n,o] | ||
| 14 | // Y[n] = conv2d(X[n], w'', pad, stride=1) | ||
| 15 | // | ||
| 16 | // Backward (per n; dw'' = conv2d_backward_weight(X[n],dY[n])): | ||
| 17 | // g[o] = Σ dw''[o,..] * w'[o,..] | ||
| 18 | // dw'[o] = demodulate ? dw''[o]*dcoef - g[o]*dcoef^3*w'[o] : dw''[o] | ||
| 19 | // dW[o] += Σ_n dw'[n,o] * s[n,i] (accumulate — caller zeros dW) | ||
| 20 | // ds[n,i] = Σ_{o,kh,kw} dw'[o,i,kh,kw] * W[o,i,kh,kw] (overwrite) | ||
| 21 | // dX[n] = conv2d_backward_input(w''[n], dY[n]) (overwrite) | ||
| 22 | |||
| 23 | #include <brotensor/tensor.h> | ||
| 24 | |||
| 25 | #include <cmath> | ||
| 26 | #include <stdexcept> | ||
| 27 | #include <string> | ||
| 28 | #include <vector> | ||
| 29 | |||
| 30 | namespace brotensor::detail::cpu { | ||
| 31 | |||
| 32 | // Reused CPU conv2d kernels (defined in conv2d.cpp, same namespace). | ||
| 33 | void conv2d_forward(const ::brotensor::Tensor& X, const ::brotensor::Tensor& Wt, | ||
| 34 | const ::brotensor::Tensor* bias, | ||
| 35 | int N, int C_in, int H, int W, int C_out, int kH, int kW, | ||
| 36 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 37 | int dil_h, int dil_w, int groups, ::brotensor::Tensor& Y); | ||
| 38 | void conv2d_backward_input(const ::brotensor::Tensor& Wt, | ||
| 39 | const ::brotensor::Tensor& dY, | ||
| 40 | int N, int C_in, int H, int W, | ||
| 41 | int C_out, int kH, int kW, | ||
| 42 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 43 | int dil_h, int dil_w, int groups, | ||
| 44 | ::brotensor::Tensor& dX); | ||
| 45 | void conv2d_backward_weight(const ::brotensor::Tensor& X, | ||
| 46 | const ::brotensor::Tensor& dY, | ||
| 47 | int N, int C_in, int H, int W, | ||
| 48 | int C_out, int kH, int kW, | ||
| 49 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 50 | int dil_h, int dil_w, int groups, | ||
| 51 | ::brotensor::Tensor& dWt); | ||
| 52 | |||
| 53 | namespace { | ||
| 54 | |||
| 55 | 92 | inline void check_fp32(const ::brotensor::Tensor& t, | |
| 56 | const char* op, const char* name) { | ||
| 57 |
1/2✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
|
92 | if (t.dtype != Dtype::FP32) { |
| 58 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": " + | |
| 59 | ✗ | name + " must be FP32 (CPU backend is " | |
| 60 | "FP32-only)"); | ||
| 61 | } | ||
| 62 | 92 | } | |
| 63 | |||
| 64 | // Non-owning CPU FP32 view over one row of a (N, cols) tensor. | ||
| 65 | 64 | inline ::brotensor::Tensor row_view(const ::brotensor::Tensor& T, int n, int cols) { | |
| 66 | 64 | float* base = const_cast<float*>(T.host_f32()) + static_cast<size_t>(n) * cols; | |
| 67 | 64 | return ::brotensor::Tensor::view(Device::CPU, base, 1, cols, Dtype::FP32); | |
| 68 | } | ||
| 69 | 40 | inline ::brotensor::Tensor row_view_mut(::brotensor::Tensor& T, int n, int cols) { | |
| 70 | 40 | float* base = T.host_f32_mut() + static_cast<size_t>(n) * cols; | |
| 71 | 40 | return ::brotensor::Tensor::view(Device::CPU, base, 1, cols, Dtype::FP32); | |
| 72 | } | ||
| 73 | |||
| 74 | } // namespace | ||
| 75 | |||
| 76 | 8 | void modulated_conv2d_forward(const ::brotensor::Tensor& X, | |
| 77 | const ::brotensor::Tensor& W, | ||
| 78 | const ::brotensor::Tensor& s, | ||
| 79 | int N, int C_in, int H, int Wd, | ||
| 80 | int C_out, int kH, int kW, | ||
| 81 | int pad_h, int pad_w, | ||
| 82 | bool demodulate, float eps, | ||
| 83 | ::brotensor::Tensor& dcoef, | ||
| 84 | ::brotensor::Tensor& Y) { | ||
| 85 | 8 | check_fp32(X, "modulated_conv2d_forward", "X"); | |
| 86 | 8 | check_fp32(W, "modulated_conv2d_forward", "W"); | |
| 87 | 8 | check_fp32(s, "modulated_conv2d_forward", "s"); | |
| 88 | 8 | const int wk = C_in * kH * kW; | |
| 89 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (X.rows != N || X.cols != C_in * H * Wd) |
| 90 | ✗ | throw std::runtime_error("modulated_conv2d_forward: X shape mismatch"); | |
| 91 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (W.rows != C_out || W.cols != wk) |
| 92 | ✗ | throw std::runtime_error("modulated_conv2d_forward: W shape mismatch"); | |
| 93 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (s.rows != N || s.cols != C_in) |
| 94 | ✗ | throw std::runtime_error("modulated_conv2d_forward: s shape mismatch"); | |
| 95 | 8 | const int H_out = H + 2 * pad_h - (kH - 1); | |
| 96 | 8 | const int W_out = Wd + 2 * pad_w - (kW - 1); | |
| 97 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (H_out <= 0 || W_out <= 0) |
| 98 | ✗ | throw std::runtime_error("modulated_conv2d_forward: non-positive output shape"); | |
| 99 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | if (dcoef.rows != N || dcoef.cols != C_out || dcoef.dtype != Dtype::FP32) |
| 100 | 8 | dcoef.resize(N, C_out, Dtype::FP32); | |
| 101 | 8 | const int out_cols = C_out * H_out * W_out; | |
| 102 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | if (Y.rows != N || Y.cols != out_cols || Y.dtype != Dtype::FP32) |
| 103 | 8 | Y.resize(N, out_cols, Dtype::FP32); | |
| 104 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (N == 0 || out_cols == 0) return; |
| 105 | |||
| 106 | 8 | const float* Wp = W.host_f32(); | |
| 107 | 8 | const float* sp = s.host_f32(); | |
| 108 | 8 | float* dcp = dcoef.host_f32_mut(); | |
| 109 | |||
| 110 | 8 | ::brotensor::Tensor Wn = ::brotensor::Tensor::zeros_on(Device::CPU, C_out, wk); | |
| 111 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | float* Wnp = Wn.host_f32_mut(); |
| 112 | |||
| 113 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
|
24 | for (int n = 0; n < N; ++n) { |
| 114 | 16 | const float* sn = sp + static_cast<size_t>(n) * C_in; | |
| 115 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 16 times.
|
80 | for (int o = 0; o < C_out; ++o) { |
| 116 | 64 | const float* Wo = Wp + static_cast<size_t>(o) * wk; | |
| 117 | 64 | float* Wno = Wnp + static_cast<size_t>(o) * wk; | |
| 118 | // w'[o,..] = W[o,..]*s[n,i]; demod coefficient from its norm. | ||
| 119 | 64 | double ss = 0.0; | |
| 120 |
2/2✓ Branch 0 taken 192 times.
✓ Branch 1 taken 64 times.
|
256 | for (int i = 0; i < C_in; ++i) { |
| 121 | 192 | const float si = sn[i]; | |
| 122 |
2/2✓ Branch 0 taken 1344 times.
✓ Branch 1 taken 192 times.
|
1536 | for (int t = 0; t < kH * kW; ++t) { |
| 123 | 1344 | const float wp = Wo[i * kH * kW + t] * si; | |
| 124 | 1344 | Wno[i * kH * kW + t] = wp; | |
| 125 | 1344 | ss += static_cast<double>(wp) * wp; | |
| 126 | 1344 | } | |
| 127 | 192 | } | |
| 128 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 24 times.
|
64 | const float d = demodulate |
| 129 | 40 | ? 1.0f / std::sqrt(static_cast<float>(ss) + eps) : 1.0f; | |
| 130 | 64 | dcp[static_cast<size_t>(n) * C_out + o] = d; | |
| 131 |
4/4✓ Branch 0 taken 24 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 888 times.
✓ Branch 3 taken 40 times.
|
952 | if (demodulate) for (int t = 0; t < wk; ++t) Wno[t] *= d; |
| 132 | 64 | } | |
| 133 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | ::brotensor::Tensor Xn = row_view(X, n, C_in * H * Wd); |
| 134 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | ::brotensor::Tensor Yn = row_view_mut(Y, n, out_cols); |
| 135 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
|
32 | conv2d_forward(Xn, Wn, nullptr, 1, C_in, H, Wd, C_out, kH, kW, |
| 136 | 16 | 1, 1, pad_h, pad_w, 1, 1, 1, Yn); | |
| 137 | 16 | } | |
| 138 | 8 | } | |
| 139 | |||
| 140 | 12 | void modulated_conv2d_backward(const ::brotensor::Tensor& X, | |
| 141 | const ::brotensor::Tensor& W, | ||
| 142 | const ::brotensor::Tensor& s, | ||
| 143 | const ::brotensor::Tensor& dcoef, | ||
| 144 | const ::brotensor::Tensor& dY, | ||
| 145 | int N, int C_in, int H, int Wd, | ||
| 146 | int C_out, int kH, int kW, | ||
| 147 | int pad_h, int pad_w, bool demodulate, float eps, | ||
| 148 | ::brotensor::Tensor& dX, | ||
| 149 | ::brotensor::Tensor& dW, | ||
| 150 | ::brotensor::Tensor& ds) { | ||
| 151 | 12 | check_fp32(X, "modulated_conv2d_backward", "X"); | |
| 152 | 12 | check_fp32(W, "modulated_conv2d_backward", "W"); | |
| 153 | 12 | check_fp32(s, "modulated_conv2d_backward", "s"); | |
| 154 | 12 | check_fp32(dcoef, "modulated_conv2d_backward", "dcoef"); | |
| 155 | 12 | check_fp32(dY, "modulated_conv2d_backward", "dY"); | |
| 156 | (void)eps; // demod coefficient is precomputed (passed in as dcoef) | ||
| 157 | 12 | const int wk = C_in * kH * kW; | |
| 158 | 12 | const int H_out = H + 2 * pad_h - (kH - 1); | |
| 159 | 12 | const int W_out = Wd + 2 * pad_w - (kW - 1); | |
| 160 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (W.rows != C_out || W.cols != wk) |
| 161 | ✗ | throw std::runtime_error("modulated_conv2d_backward: W shape mismatch"); | |
| 162 | // dW is an optional output: an uncommitted (data == nullptr) dW means | ||
| 163 | // "skip the weight gradient" (mirrors the CUDA backend — used by inversion, | ||
| 164 | // which freezes the weights). When committed it must be FP32 and shaped. | ||
| 165 | 12 | const bool want_dW = (dW.data != nullptr); | |
| 166 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | if (want_dW) { |
| 167 | 8 | check_fp32(dW, "modulated_conv2d_backward", "dW"); | |
| 168 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (dW.rows != C_out || dW.cols != wk) |
| 169 | ✗ | throw std::runtime_error("modulated_conv2d_backward: dW shape mismatch"); | |
| 170 | 8 | } | |
| 171 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
12 | if (dX.rows != N || dX.cols != C_in * H * Wd || dX.dtype != Dtype::FP32) |
| 172 | 12 | dX.resize(N, C_in * H * Wd, Dtype::FP32); | |
| 173 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
12 | if (ds.rows != N || ds.cols != C_in || ds.dtype != Dtype::FP32) |
| 174 | 12 | ds.resize(N, C_in, Dtype::FP32); | |
| 175 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (N == 0) return; |
| 176 | |||
| 177 | 12 | const float* Wp = W.host_f32(); | |
| 178 | 12 | const float* sp = s.host_f32(); | |
| 179 | 12 | const float* dcp = dcoef.host_f32(); | |
| 180 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
12 | float* dWp = want_dW ? dW.host_f32_mut() : nullptr; // accumulate (optional) |
| 181 | 12 | float* dsp = ds.host_f32_mut(); // overwrite | |
| 182 | |||
| 183 | 12 | const int out_cols = C_out * H_out * W_out; | |
| 184 | 12 | ::brotensor::Tensor Wpp = ::brotensor::Tensor::zeros_on(Device::CPU, C_out, wk); // w'' | |
| 185 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | ::brotensor::Tensor Wpr = ::brotensor::Tensor::zeros_on(Device::CPU, C_out, wk); // w' |
| 186 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | ::brotensor::Tensor dWpp = ::brotensor::Tensor::zeros_on(Device::CPU, C_out, wk); |
| 187 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | std::vector<float> dWpr(static_cast<size_t>(C_out) * wk); // dw' |
| 188 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | float* Wppp = Wpp.host_f32_mut(); |
| 189 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | float* Wprp = Wpr.host_f32_mut(); |
| 190 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | float* dWppp = dWpp.host_f32_mut(); |
| 191 | |||
| 192 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 12 times.
|
36 | for (int n = 0; n < N; ++n) { |
| 193 | 24 | const float* sn = sp + static_cast<size_t>(n) * C_in; | |
| 194 | 24 | const float* dcn = dcp + static_cast<size_t>(n) * C_out; | |
| 195 | // Rebuild w' and w''. | ||
| 196 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 24 times.
|
120 | for (int o = 0; o < C_out; ++o) { |
| 197 | 96 | const float* Wo = Wp + static_cast<size_t>(o) * wk; | |
| 198 | 96 | const float d = dcn[o]; | |
| 199 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 96 times.
|
384 | for (int i = 0; i < C_in; ++i) { |
| 200 | 288 | const float si = sn[i]; | |
| 201 |
2/2✓ Branch 0 taken 1824 times.
✓ Branch 1 taken 288 times.
|
2112 | for (int t = 0; t < kH * kW; ++t) { |
| 202 | 1824 | const int col = i * kH * kW + t; | |
| 203 | 1824 | const float wp = Wo[col] * si; | |
| 204 | 1824 | Wprp[static_cast<size_t>(o) * wk + col] = wp; | |
| 205 | 1824 | Wppp[static_cast<size_t>(o) * wk + col] = wp * d; | |
| 206 | 1824 | } | |
| 207 | 288 | } | |
| 208 | 96 | } | |
| 209 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | ::brotensor::Tensor Xn = row_view(X, n, C_in * H * Wd); |
| 210 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | ::brotensor::Tensor dYn = row_view(dY, n, out_cols); |
| 211 | // dw'' (conv2d_backward_weight accumulates → zero first), and dX[n]. | ||
| 212 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | dWpp.zero(); |
| 213 |
2/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
48 | conv2d_backward_weight(Xn, dYn, 1, C_in, H, Wd, C_out, kH, kW, |
| 214 | 24 | 1, 1, pad_h, pad_w, 1, 1, 1, dWpp); | |
| 215 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | ::brotensor::Tensor dXn = row_view_mut(dX, n, C_in * H * Wd); |
| 216 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
|
48 | conv2d_backward_input(Wpp, dYn, 1, C_in, H, Wd, C_out, kH, kW, |
| 217 | 24 | 1, 1, pad_h, pad_w, 1, 1, 1, dXn); | |
| 218 | // Through demod → dw'. | ||
| 219 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 24 times.
|
120 | for (int o = 0; o < C_out; ++o) { |
| 220 | 96 | const size_t ob = static_cast<size_t>(o) * wk; | |
| 221 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 40 times.
|
96 | if (demodulate) { |
| 222 | 56 | const float d = dcn[o]; | |
| 223 | 56 | double g = 0.0; | |
| 224 |
2/2✓ Branch 0 taken 1128 times.
✓ Branch 1 taken 56 times.
|
1184 | for (int t = 0; t < wk; ++t) |
| 225 | 1128 | g += static_cast<double>(dWppp[ob + t]) * Wprp[ob + t]; | |
| 226 | 56 | const float gd3 = static_cast<float>(g) * d * d * d; | |
| 227 |
2/2✓ Branch 0 taken 1128 times.
✓ Branch 1 taken 56 times.
|
1184 | for (int t = 0; t < wk; ++t) |
| 228 | 1128 | dWpr[ob + t] = dWppp[ob + t] * d - gd3 * Wprp[ob + t]; | |
| 229 | 56 | } else { | |
| 230 |
2/2✓ Branch 0 taken 696 times.
✓ Branch 1 taken 40 times.
|
736 | for (int t = 0; t < wk; ++t) dWpr[ob + t] = dWppp[ob + t]; |
| 231 | } | ||
| 232 | 96 | } | |
| 233 | // Accumulate dW and write ds[n]. | ||
| 234 | 24 | float* dsn = dsp + static_cast<size_t>(n) * C_in; | |
| 235 |
2/2✓ Branch 0 taken 72 times.
✓ Branch 1 taken 24 times.
|
96 | for (int i = 0; i < C_in; ++i) dsn[i] = 0.0f; |
| 236 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 24 times.
|
120 | for (int o = 0; o < C_out; ++o) { |
| 237 | 96 | const float* Wo = Wp + static_cast<size_t>(o) * wk; | |
| 238 | 96 | const size_t ob = static_cast<size_t>(o) * wk; | |
| 239 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 96 times.
|
384 | for (int i = 0; i < C_in; ++i) { |
| 240 | 288 | const float si = sn[i]; | |
| 241 | 288 | double ds_acc = 0.0; | |
| 242 |
2/2✓ Branch 0 taken 1824 times.
✓ Branch 1 taken 288 times.
|
2112 | for (int t = 0; t < kH * kW; ++t) { |
| 243 | 1824 | const int col = i * kH * kW + t; | |
| 244 | 1824 | const float dwp = dWpr[ob + col]; | |
| 245 |
2/2✓ Branch 0 taken 480 times.
✓ Branch 1 taken 1344 times.
|
1824 | if (want_dW) dWp[ob + col] += dwp * si; // accumulate dW (optional) |
| 246 | 1824 | ds_acc += static_cast<double>(dwp) * Wo[col]; | |
| 247 | 1824 | } | |
| 248 | 288 | dsn[i] += static_cast<float>(ds_acc); // overwrite ds[n,i] | |
| 249 | 288 | } | |
| 250 | 96 | } | |
| 251 | 24 | } | |
| 252 | 12 | } | |
| 253 | |||
| 254 | } // namespace brotensor::detail::cpu | ||
| 255 |