src/cpu/resblock.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // CPU backend — diffusion ResBlock (CHUNK 6). | ||
| 2 | // | ||
| 3 | // Ground truth: src/cuda/resblock.cu — the FP32-weight resblock_forward and | ||
| 4 | // resblock_backward (NOT resblock_forward_int8w_fp16). | ||
| 5 | // | ||
| 6 | // DTYPE DECISIONS | ||
| 7 | // The CUDA resblock runs FP16 internally (GroupNorm/SiLU/conv2d all FP16, | ||
| 8 | // FP32 accumulators). The CPU backend is FP32-only (per CLAUDE.md). Both | ||
| 9 | // CPU impls run the FP32 scalar math; the parity test quantises inputs | ||
| 10 | // through FP16 so both backends start identical and compares with a loose | ||
| 11 | // FP16-scale tolerance (the test_resblock.cpp smoke-test envelope: | ||
| 12 | // atol=1e-2, rtol=1e-2). A diffusion ResBlock is a long composite chain | ||
| 13 | // (two GroupNorms + two 3x3 convs), so FP16 rounding compounds — a tight | ||
| 14 | // FP32 tolerance would not survive the FP16/FP32 cross-backend comparison. | ||
| 15 | // | ||
| 16 | // EXACT OP ORDER (verified against resblock.cu) | ||
| 17 | // forward: | ||
| 18 | // h1 = SiLU(GroupNorm(X, gamma1, beta1)) [C_in] | ||
| 19 | // h2 = conv2d(h1, W1, b1, 3x3 stride-1 pad-1) [C_out] | ||
| 20 | // if t_emb_shift: h2 += broadcast(t_emb_shift) — applied AFTER conv1, | ||
| 21 | // BEFORE GroupNorm2; (N, C_out) per-(n,c) or (C_out,) per-c shift. | ||
| 22 | // h3 = SiLU(GroupNorm(h2, gamma2, beta2)) [C_out] | ||
| 23 | // Y = conv2d(h3, W2, b2, 3x3 stride-1 pad-1) [C_out] | ||
| 24 | // skip = (Wskip ? conv2d(X, Wskip, bskip, 1x1) : X) | ||
| 25 | // Y += skip | ||
| 26 | // Wskip is required when C_in != C_out; null skip is the identity (C_in | ||
| 27 | // must equal C_out). | ||
| 28 | // | ||
| 29 | // ACCUMULATION (verified against resblock.cu's composition of the conv2d / | ||
| 30 | // group_norm backward kernels) | ||
| 31 | // * dX — OVERWRITTEN by the GN1 backward, then the skip | ||
| 32 | // path is added on top (dX += dY or dX += dX_skip). | ||
| 33 | // * dGamma1/dBeta1/2 — ACCUMULATE (+=) (group_norm_backward contract). | ||
| 34 | // * dW1/dW2/dWskip — ACCUMULATE (+=) (conv2d_backward_weight). | ||
| 35 | // * db1/db2/dbskip — ACCUMULATE (+=) (conv2d_backward_bias). | ||
| 36 | // * dt_emb_shift — ACCUMULATES (+=) (CUDA folds prev value when | ||
| 37 | // reducing dh2 over the spatial / batch axes). | ||
| 38 | // All grad buffers are caller-zeroed (or pre-seeded to test accumulation). | ||
| 39 | |||
| 40 | #include <brotensor/tensor.h> | ||
| 41 | |||
| 42 | #include <cmath> | ||
| 43 | #include <stdexcept> | ||
| 44 | #include <vector> | ||
| 45 | |||
| 46 | namespace brotensor::detail::cpu { | ||
| 47 | |||
| 48 | // CPU primitives from sibling chunk files. | ||
| 49 | void group_norm_forward(const ::brotensor::Tensor& X, | ||
| 50 | const ::brotensor::Tensor& gamma, | ||
| 51 | const ::brotensor::Tensor& beta, | ||
| 52 | int N, int C, int H, int W, int num_groups, | ||
| 53 | float eps, ::brotensor::Tensor& Y); | ||
| 54 | void group_norm_backward(const ::brotensor::Tensor& X, | ||
| 55 | const ::brotensor::Tensor& gamma, | ||
| 56 | const ::brotensor::Tensor& dY, | ||
| 57 | int N, int C, int H, int W, int num_groups, float eps, | ||
| 58 | ::brotensor::Tensor& dX, ::brotensor::Tensor& dGamma, | ||
| 59 | ::brotensor::Tensor& dBeta); | ||
| 60 | void silu_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y); | ||
| 61 | void silu_backward(const ::brotensor::Tensor& x, const ::brotensor::Tensor& dY, | ||
| 62 | ::brotensor::Tensor& dX); | ||
| 63 | void conv2d_forward(const ::brotensor::Tensor& X, const ::brotensor::Tensor& Wt, | ||
| 64 | const ::brotensor::Tensor* bias, | ||
| 65 | int N, int C_in, int H, int W, int C_out, int kH, int kW, | ||
| 66 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 67 | int dil_h, int dil_w, int groups, ::brotensor::Tensor& Y); | ||
| 68 | void conv2d_backward_input(const ::brotensor::Tensor& Wt, | ||
| 69 | const ::brotensor::Tensor& dY, | ||
| 70 | int N, int C_in, int H, int W, | ||
| 71 | int C_out, int kH, int kW, | ||
| 72 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 73 | int dil_h, int dil_w, int groups, | ||
| 74 | ::brotensor::Tensor& dX); | ||
| 75 | void conv2d_backward_weight(const ::brotensor::Tensor& X, | ||
| 76 | const ::brotensor::Tensor& dY, | ||
| 77 | int N, int C_in, int H, int W, | ||
| 78 | int C_out, int kH, int kW, | ||
| 79 | int stride_h, int stride_w, int pad_h, int pad_w, | ||
| 80 | int dil_h, int dil_w, int groups, | ||
| 81 | ::brotensor::Tensor& dWt); | ||
| 82 | void conv2d_backward_bias(const ::brotensor::Tensor& dY, | ||
| 83 | int N, int C_out, int H_out, int W_out, | ||
| 84 | ::brotensor::Tensor& dB); | ||
| 85 | void add_inplace(::brotensor::Tensor& y, const ::brotensor::Tensor& x); | ||
| 86 | |||
| 87 | namespace { | ||
| 88 | |||
| 89 | using ::brotensor::Tensor; | ||
| 90 | using ::brotensor::Dtype; | ||
| 91 | |||
| 92 | // Resolve a t_emb_shift tensor's broadcast mode. Returns true and sets has_N | ||
| 93 | // if the shape is (N, C_out); false (per-channel) if (C_out,) / (C_out,1) / | ||
| 94 | // (1,C_out). Throws on any other shape — mirrors resblock.cu. | ||
| 95 | 8 | bool temb_has_N(const Tensor& t, int N, int C_out) { | |
| 96 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (t.rows == N && t.cols == C_out) return true; |
| 97 | ✗ | if ((t.rows == C_out && t.cols == 1) || | |
| 98 | ✗ | (t.rows == 1 && t.cols == C_out) || | |
| 99 | ✗ | t.size() == C_out) | |
| 100 | ✗ | return false; | |
| 101 | ✗ | throw std::runtime_error( | |
| 102 | "resblock: t_emb_shift shape must be (N, C_out) or (C_out,)"); | ||
| 103 | 8 | } | |
| 104 | |||
| 105 | // h2[n, c, p] += t_emb_shift broadcast over the spatial axis. | ||
| 106 | 8 | void add_temb_shift(float* h2, const float* shift, | |
| 107 | int N, int C_out, int spatial, bool has_N) { | ||
| 108 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 8 times.
|
18 | for (int n = 0; n < N; ++n) { |
| 109 |
2/2✓ Branch 0 taken 304 times.
✓ Branch 1 taken 10 times.
|
314 | for (int c = 0; c < C_out; ++c) { |
| 110 |
1/2✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
|
304 | const float sv = has_N ? shift[n * C_out + c] : shift[c]; |
| 111 | 304 | float* row = h2 + (static_cast<std::size_t>(n) * C_out + c) * spatial; | |
| 112 |
2/2✓ Branch 0 taken 17920 times.
✓ Branch 1 taken 304 times.
|
18224 | for (int p = 0; p < spatial; ++p) row[p] += sv; |
| 113 | 304 | } | |
| 114 | 10 | } | |
| 115 | 8 | } | |
| 116 | |||
| 117 | } // namespace | ||
| 118 | |||
| 119 | // ─── resblock_forward ────────────────────────────────────────────────────── | ||
| 120 | |||
| 121 | 8 | void resblock_forward(const ::brotensor::Tensor& X, | |
| 122 | const ::brotensor::Tensor& gamma1, | ||
| 123 | const ::brotensor::Tensor& beta1, | ||
| 124 | const ::brotensor::Tensor& W1, | ||
| 125 | const ::brotensor::Tensor* b1, | ||
| 126 | const ::brotensor::Tensor* t_emb_shift, | ||
| 127 | const ::brotensor::Tensor& gamma2, | ||
| 128 | const ::brotensor::Tensor& beta2, | ||
| 129 | const ::brotensor::Tensor& W2, | ||
| 130 | const ::brotensor::Tensor* b2, | ||
| 131 | const ::brotensor::Tensor* Wskip, | ||
| 132 | const ::brotensor::Tensor* bskip, | ||
| 133 | int N, int C_in, int C_out, int H, int W, | ||
| 134 | int num_groups, float eps, | ||
| 135 | ::brotensor::Tensor& Y) { | ||
| 136 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (num_groups <= 0 || C_in % num_groups != 0 || C_out % num_groups != 0) |
| 137 | ✗ | throw std::runtime_error("resblock_forward: num_groups must divide C_in and C_out"); | |
| 138 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | if (Wskip == nullptr && C_in != C_out) |
| 139 | ✗ | throw std::runtime_error("resblock_forward: Wskip required when C_in != C_out"); | |
| 140 | 8 | const int spatial = H * W; | |
| 141 | 8 | const int out_cols = C_out * spatial; | |
| 142 |
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) |
| 143 | 8 | Y.resize(N, out_cols, Dtype::FP32); | |
| 144 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (N == 0 || spatial == 0) return; |
| 145 | |||
| 146 | // Leg 1: GN1 → SiLU. | ||
| 147 | 8 | Tensor h1; | |
| 148 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | group_norm_forward(X, gamma1, beta1, N, C_in, H, W, num_groups, eps, h1); |
| 149 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_forward(h1, h1); |
| 150 | |||
| 151 | // Conv1: 3x3 same. | ||
| 152 | 8 | Tensor h2; | |
| 153 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | conv2d_forward(h1, W1, b1, N, C_in, H, W, C_out, 3, 3, |
| 154 | 1, 1, 1, 1, 1, 1, 1, h2); | ||
| 155 | |||
| 156 | // Optional t_emb shift (after conv1, before GN2). | ||
| 157 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (t_emb_shift) { |
| 158 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const bool has_N = temb_has_N(*t_emb_shift, N, C_out); |
| 159 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | add_temb_shift(h2.host_f32_mut(), t_emb_shift->host_f32(), |
| 160 | 4 | N, C_out, spatial, has_N); | |
| 161 | 4 | } | |
| 162 | |||
| 163 | // Leg 2: GN2 → SiLU. | ||
| 164 | 8 | Tensor h3; | |
| 165 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | group_norm_forward(h2, gamma2, beta2, N, C_out, H, W, num_groups, eps, h3); |
| 166 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_forward(h3, h3); |
| 167 | |||
| 168 | // Conv2: 3x3 same → Y. | ||
| 169 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | conv2d_forward(h3, W2, b2, N, C_out, H, W, C_out, 3, 3, |
| 170 | 8 | 1, 1, 1, 1, 1, 1, 1, Y); | |
| 171 | |||
| 172 | // Skip path. | ||
| 173 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (Wskip == nullptr) { |
| 174 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | add_inplace(Y, X); |
| 175 | 4 | } else { | |
| 176 | 4 | Tensor skip; | |
| 177 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | conv2d_forward(X, *Wskip, bskip, N, C_in, H, W, C_out, 1, 1, |
| 178 | 1, 1, 0, 0, 1, 1, 1, skip); | ||
| 179 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | add_inplace(Y, skip); |
| 180 | 4 | } | |
| 181 | 8 | } | |
| 182 | |||
| 183 | // ─── resblock_backward ───────────────────────────────────────────────────── | ||
| 184 | |||
| 185 | 8 | void resblock_backward(const ::brotensor::Tensor& X, | |
| 186 | const ::brotensor::Tensor& gamma1, | ||
| 187 | const ::brotensor::Tensor& beta1, | ||
| 188 | const ::brotensor::Tensor& W1, | ||
| 189 | const ::brotensor::Tensor* b1, | ||
| 190 | const ::brotensor::Tensor* t_emb_shift, | ||
| 191 | const ::brotensor::Tensor& gamma2, | ||
| 192 | const ::brotensor::Tensor& beta2, | ||
| 193 | const ::brotensor::Tensor& W2, | ||
| 194 | const ::brotensor::Tensor* b2, | ||
| 195 | const ::brotensor::Tensor* Wskip, | ||
| 196 | const ::brotensor::Tensor* bskip, | ||
| 197 | int N, int C_in, int C_out, int H, int W, | ||
| 198 | int num_groups, float eps, | ||
| 199 | const ::brotensor::Tensor& dY, | ||
| 200 | ::brotensor::Tensor& dX, | ||
| 201 | ::brotensor::Tensor& dGamma1, | ||
| 202 | ::brotensor::Tensor& dBeta1, | ||
| 203 | ::brotensor::Tensor& dW1, | ||
| 204 | ::brotensor::Tensor* db1, | ||
| 205 | ::brotensor::Tensor* dt_emb_shift, | ||
| 206 | ::brotensor::Tensor& dGamma2, | ||
| 207 | ::brotensor::Tensor& dBeta2, | ||
| 208 | ::brotensor::Tensor& dW2, | ||
| 209 | ::brotensor::Tensor* db2, | ||
| 210 | ::brotensor::Tensor* dWskip, | ||
| 211 | ::brotensor::Tensor* dbskip) { | ||
| 212 | // Forward biases b2/bskip do not affect any backward quantity (the conv2 | ||
| 213 | // and skip-conv outputs are never recomputed in the backward, and a bias | ||
| 214 | // grad is a pure reduction of the downstream grad); accepted for API | ||
| 215 | // symmetry with the forward and resblock.cu. b1 IS used — conv1 is | ||
| 216 | // recomputed to obtain h2. | ||
| 217 | (void)b2; (void)bskip; | ||
| 218 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | if (Wskip == nullptr && C_in != C_out) |
| 219 | ✗ | throw std::runtime_error("resblock_backward: Wskip required when C_in != C_out"); | |
| 220 | 8 | const int spatial = H * W; | |
| 221 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (dY.rows != N || dY.cols != C_out * spatial) |
| 222 | ✗ | throw std::runtime_error("resblock_backward: dY shape mismatch"); | |
| 223 |
3/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
|
8 | if (dX.rows != N || dX.cols != C_in * spatial || dX.dtype != Dtype::FP32) |
| 224 | ✗ | dX.resize(N, C_in * spatial, Dtype::FP32); | |
| 225 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (N == 0 || spatial == 0) return; |
| 226 | |||
| 227 | // ── Recompute forward intermediates. ── | ||
| 228 | 8 | Tensor h1_pre_silu, h1; | |
| 229 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | group_norm_forward(X, gamma1, beta1, N, C_in, H, W, num_groups, eps, |
| 230 | h1_pre_silu); | ||
| 231 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_forward(h1_pre_silu, h1); |
| 232 | |||
| 233 | 8 | Tensor h2; | |
| 234 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | conv2d_forward(h1, W1, b1, N, C_in, H, W, C_out, 3, 3, |
| 235 | 1, 1, 1, 1, 1, 1, 1, h2); | ||
| 236 | 8 | bool temb_N = false; | |
| 237 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (t_emb_shift) { |
| 238 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | temb_N = temb_has_N(*t_emb_shift, N, C_out); |
| 239 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | add_temb_shift(h2.host_f32_mut(), t_emb_shift->host_f32(), |
| 240 | 4 | N, C_out, spatial, temb_N); | |
| 241 | 4 | } | |
| 242 | |||
| 243 | 8 | Tensor h3_pre_silu, h3; | |
| 244 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | group_norm_forward(h2, gamma2, beta2, N, C_out, H, W, num_groups, eps, |
| 245 | h3_pre_silu); | ||
| 246 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_forward(h3_pre_silu, h3); |
| 247 | |||
| 248 | // ── Conv2 backward: dh3 (input grad), dW2 +=, db2 +=. ── | ||
| 249 | 8 | Tensor dh3; | |
| 250 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | conv2d_backward_input(W2, dY, N, C_out, H, W, C_out, 3, 3, |
| 251 | 1, 1, 1, 1, 1, 1, 1, dh3); | ||
| 252 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | conv2d_backward_weight(h3, dY, N, C_out, H, W, C_out, 3, 3, |
| 253 | 8 | 1, 1, 1, 1, 1, 1, 1, dW2); | |
| 254 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | if (db2) conv2d_backward_bias(dY, N, C_out, H, W, *db2); |
| 255 | |||
| 256 | // ── SiLU2 backward over h3_pre_silu. ── | ||
| 257 | 8 | Tensor dh3_pre_silu; | |
| 258 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_backward(h3_pre_silu, dh3, dh3_pre_silu); |
| 259 | |||
| 260 | // ── GN2 backward: dh2 (overwritten), dGamma2/dBeta2 accumulate. ── | ||
| 261 | 8 | Tensor dh2; | |
| 262 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | group_norm_backward(h2, gamma2, dh3_pre_silu, N, C_out, H, W, |
| 263 | 8 | num_groups, eps, dh2, dGamma2, dBeta2); | |
| 264 | |||
| 265 | // ── t_emb_shift backward: reduce dh2 over the spatial (and, for the | ||
| 266 | // per-channel case, batch) axes; accumulate into dt_emb_shift. ── | ||
| 267 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | if (t_emb_shift && dt_emb_shift) { |
| 268 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const float* dh2p = dh2.host_f32(); |
| 269 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | float* dtp = dt_emb_shift->host_f32_mut(); |
| 270 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (temb_N) { |
| 271 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | for (int n = 0; n < N; ++n) { |
| 272 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 4 times.
|
52 | for (int c = 0; c < C_out; ++c) { |
| 273 | 48 | float acc = 0.0f; | |
| 274 | 48 | const float* row = | |
| 275 | 96 | dh2p + (static_cast<std::size_t>(n) * C_out + c) * | |
| 276 | 48 | spatial; | |
| 277 |
2/2✓ Branch 0 taken 1536 times.
✓ Branch 1 taken 48 times.
|
1584 | for (int p = 0; p < spatial; ++p) acc += row[p]; |
| 278 | 48 | dtp[n * C_out + c] += acc; // accumulate | |
| 279 | 48 | } | |
| 280 | 4 | } | |
| 281 | 4 | } else { | |
| 282 | ✗ | for (int c = 0; c < C_out; ++c) { | |
| 283 | ✗ | float acc = 0.0f; | |
| 284 | ✗ | for (int n = 0; n < N; ++n) { | |
| 285 | ✗ | const float* row = | |
| 286 | ✗ | dh2p + (static_cast<std::size_t>(n) * C_out + c) * | |
| 287 | ✗ | spatial; | |
| 288 | ✗ | for (int p = 0; p < spatial; ++p) acc += row[p]; | |
| 289 | ✗ | } | |
| 290 | ✗ | dtp[c] += acc; // accumulate | |
| 291 | ✗ | } | |
| 292 | } | ||
| 293 | 4 | } | |
| 294 | |||
| 295 | // ── Conv1 backward: dh1, dW1 +=, db1 +=. ── | ||
| 296 | 8 | Tensor dh1; | |
| 297 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | conv2d_backward_input(W1, dh2, N, C_in, H, W, C_out, 3, 3, |
| 298 | 1, 1, 1, 1, 1, 1, 1, dh1); | ||
| 299 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | conv2d_backward_weight(h1, dh2, N, C_in, H, W, C_out, 3, 3, |
| 300 | 8 | 1, 1, 1, 1, 1, 1, 1, dW1); | |
| 301 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | if (db1) conv2d_backward_bias(dh2, N, C_out, H, W, *db1); |
| 302 | |||
| 303 | // ── SiLU1 backward over h1_pre_silu. ── | ||
| 304 | 8 | Tensor dh1_pre_silu; | |
| 305 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | silu_backward(h1_pre_silu, dh1, dh1_pre_silu); |
| 306 | |||
| 307 | // ── GN1 backward: dX (overwritten), dGamma1/dBeta1 accumulate. ── | ||
| 308 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | group_norm_backward(X, gamma1, dh1_pre_silu, N, C_in, H, W, |
| 309 | 8 | num_groups, eps, dX, dGamma1, dBeta1); | |
| 310 | |||
| 311 | // ── Skip path backward, then sum into dX. ── | ||
| 312 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (Wskip == nullptr) { |
| 313 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | add_inplace(dX, dY); // identity skip: dX += dY. |
| 314 | 4 | } else { | |
| 315 | 4 | Tensor dX_skip; | |
| 316 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | conv2d_backward_input(*Wskip, dY, N, C_in, H, W, C_out, 1, 1, |
| 317 | 1, 1, 0, 0, 1, 1, 1, dX_skip); | ||
| 318 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (dWskip) |
| 319 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
8 | conv2d_backward_weight(X, dY, N, C_in, H, W, C_out, 1, 1, |
| 320 | 4 | 1, 1, 0, 0, 1, 1, 1, *dWskip); | |
| 321 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (dbskip) |
| 322 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | conv2d_backward_bias(dY, N, C_out, H, W, *dbskip); |
| 323 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | add_inplace(dX, dX_skip); |
| 324 | 4 | } | |
| 325 | 8 | } | |
| 326 | |||
| 327 | } // namespace brotensor::detail::cpu | ||
| 328 |