src/cpu/lstm.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // CPU implementation of the trainable LSTM cell (ops/lstm.h) — training | ||
| 2 | // forward with per-step caching, and full back-propagation-through-time. | ||
| 3 | // | ||
| 4 | // PyTorch nn.LSTM weight layout: W_ih (4H,I), W_hh (4H,H), gate-row blocks in | ||
| 5 | // order [input, forget, cell, output]; gate g uses tanh, the rest sigmoid. | ||
| 6 | // CPU-resident, FP32 throughout. | ||
| 7 | |||
| 8 | #include <brotensor/tensor.h> | ||
| 9 | |||
| 10 | #include <cmath> | ||
| 11 | #include <stdexcept> | ||
| 12 | #include <string> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | namespace brotensor::detail::cpu { | ||
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | 108162 | inline float sigmoidf(float x) { return 1.0f / (1.0f + std::exp(-x)); } | |
| 20 | |||
| 21 | 11578 | void require_cpu_f32(const ::brotensor::Tensor& t, const char* what) { | |
| 22 |
1/2✓ Branch 0 taken 11578 times.
✗ Branch 1 not taken.
|
11578 | if (t.device != ::brotensor::Device::CPU || |
| 23 | 11578 | t.dtype != ::brotensor::Dtype::FP32) { | |
| 24 | ✗ | throw std::runtime_error(std::string("lstm: ") + what + | |
| 25 | " must be CPU FP32"); | ||
| 26 | } | ||
| 27 | 11578 | } | |
| 28 | |||
| 29 | // An optional bias/state tensor: present iff non-null and non-empty. | ||
| 30 | 4418 | const float* opt_ptr(const ::brotensor::Tensor* t, const char* what, | |
| 31 | int rows, int cols) { | ||
| 32 |
3/4✓ Branch 0 taken 4398 times.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4398 times.
|
4418 | if (!t || t->empty()) return nullptr; |
| 33 | 4398 | require_cpu_f32(*t, what); | |
| 34 |
1/2✓ Branch 0 taken 4398 times.
✗ Branch 1 not taken.
|
4398 | if (t->rows != rows || t->cols != cols) { |
| 35 | ✗ | throw std::runtime_error(std::string("lstm: ") + what + " has wrong shape"); | |
| 36 | } | ||
| 37 | 4398 | return t->host_f32(); | |
| 38 | 4418 | } | |
| 39 | |||
| 40 | } // namespace | ||
| 41 | |||
| 42 | 901 | void lstm_forward_train(const ::brotensor::Tensor& X, const ::brotensor::Tensor& W_ih, | |
| 43 | const ::brotensor::Tensor& W_hh, | ||
| 44 | const ::brotensor::Tensor* b_ih, const ::brotensor::Tensor* b_hh, | ||
| 45 | const ::brotensor::Tensor* h0, const ::brotensor::Tensor* c0, | ||
| 46 | int T, int B, | ||
| 47 | ::brotensor::Tensor& Y, ::brotensor::Tensor& gates, | ||
| 48 | ::brotensor::Tensor& C, | ||
| 49 | ::brotensor::Tensor* hT, ::brotensor::Tensor* cT) { | ||
| 50 | 901 | require_cpu_f32(X, "X"); | |
| 51 | 901 | require_cpu_f32(W_ih, "W_ih"); | |
| 52 | 901 | require_cpu_f32(W_hh, "W_hh"); | |
| 53 | 901 | const int H = W_hh.cols; | |
| 54 | 901 | const int I = W_ih.cols; | |
| 55 | 901 | const int G = 4 * H; | |
| 56 |
1/2✓ Branch 0 taken 901 times.
✗ Branch 1 not taken.
|
901 | if (T <= 0 || B <= 0) |
| 57 | ✗ | throw std::runtime_error("lstm_forward_train: T and B must be > 0"); | |
| 58 |
1/2✓ Branch 0 taken 901 times.
✗ Branch 1 not taken.
|
901 | if (W_ih.rows != G || W_hh.rows != G) |
| 59 | ✗ | throw std::runtime_error("lstm_forward_train: W_ih/W_hh must have 4*H rows"); | |
| 60 |
1/2✓ Branch 0 taken 901 times.
✗ Branch 1 not taken.
|
901 | if (X.rows != T * B || X.cols != I) |
| 61 | ✗ | throw std::runtime_error("lstm_forward_train: X must be (T*B, I)"); | |
| 62 | |||
| 63 | 901 | const float* bih = opt_ptr(b_ih, "b_ih", G, 1); | |
| 64 | 901 | const float* bhh = opt_ptr(b_hh, "b_hh", G, 1); | |
| 65 | 901 | const float* h0p = opt_ptr(h0, "h0", B, H); | |
| 66 | 901 | const float* c0p = opt_ptr(c0, "c0", B, H); | |
| 67 | |||
| 68 | 901 | Y.resize(T * B, H); | |
| 69 | 901 | gates.resize(T * B, G); | |
| 70 | 901 | C.resize(T * B, H); | |
| 71 |
2/2✓ Branch 0 taken 895 times.
✓ Branch 1 taken 6 times.
|
901 | if (hT) hT->resize(B, H); |
| 72 |
2/2✓ Branch 0 taken 895 times.
✓ Branch 1 taken 6 times.
|
901 | if (cT) cT->resize(B, H); |
| 73 | |||
| 74 | 901 | const float* xp = X.host_f32(); | |
| 75 | 901 | const float* wih = W_ih.host_f32(); | |
| 76 | 901 | const float* whh = W_hh.host_f32(); | |
| 77 | 901 | float* yp = Y.host_f32_mut(); | |
| 78 | 901 | float* gp = gates.host_f32_mut(); | |
| 79 | 901 | float* cp = C.host_f32_mut(); | |
| 80 | |||
| 81 |
2/2✓ Branch 0 taken 3598 times.
✓ Branch 1 taken 901 times.
|
4499 | for (int t = 0; t < T; ++t) { |
| 82 |
2/2✓ Branch 0 taken 7200 times.
✓ Branch 1 taken 3598 times.
|
10798 | for (int b = 0; b < B; ++b) { |
| 83 | 7200 | const std::size_t row = static_cast<std::size_t>(t) * B + b; | |
| 84 | 7200 | const float* x = xp + row * I; | |
| 85 |
4/4✓ Branch 0 taken 5398 times.
✓ Branch 1 taken 1802 times.
✓ Branch 2 taken 1796 times.
✓ Branch 3 taken 6 times.
|
7200 | const float* hprev = (t == 0) ? (h0p ? h0p + static_cast<std::size_t>(b) * H : nullptr) |
| 86 | 5398 | : yp + (static_cast<std::size_t>(t - 1) * B + b) * H; | |
| 87 |
4/4✓ Branch 0 taken 5398 times.
✓ Branch 1 taken 1802 times.
✓ Branch 2 taken 1796 times.
✓ Branch 3 taken 6 times.
|
7200 | const float* cprev = (t == 0) ? (c0p ? c0p + static_cast<std::size_t>(b) * H : nullptr) |
| 88 | 5398 | : cp + (static_cast<std::size_t>(t - 1) * B + b) * H; | |
| 89 | 7200 | float* grow = gp + row * G; // post-activation [i|f|g|o] | |
| 90 | 7200 | float* crow = cp + row * H; | |
| 91 | 7200 | float* hrow = yp + row * H; | |
| 92 | |||
| 93 |
2/2✓ Branch 0 taken 36054 times.
✓ Branch 1 taken 7200 times.
|
43254 | for (int n = 0; n < H; ++n) { |
| 94 |
2/2✓ Branch 0 taken 35950 times.
✓ Branch 1 taken 104 times.
|
36054 | float zi = bih ? bih[n] : 0.0f; |
| 95 |
2/2✓ Branch 0 taken 35950 times.
✓ Branch 1 taken 104 times.
|
36054 | float zf = bih ? bih[H + n] : 0.0f; |
| 96 |
2/2✓ Branch 0 taken 35950 times.
✓ Branch 1 taken 104 times.
|
36054 | float zg = bih ? bih[2 * H + n] : 0.0f; |
| 97 |
2/2✓ Branch 0 taken 35950 times.
✓ Branch 1 taken 104 times.
|
36054 | float zo = bih ? bih[3 * H + n] : 0.0f; |
| 98 |
2/2✓ Branch 0 taken 104 times.
✓ Branch 1 taken 35950 times.
|
36054 | if (bhh) { zi += bhh[n]; zf += bhh[H + n]; zg += bhh[2 * H + n]; zo += bhh[3 * H + n]; } |
| 99 | |||
| 100 | 36054 | const float* wi = wih + static_cast<std::size_t>(n) * I; | |
| 101 | 36054 | const float* wf = wih + static_cast<std::size_t>(H + n) * I; | |
| 102 | 36054 | const float* wg = wih + static_cast<std::size_t>(2 * H + n) * I; | |
| 103 | 36054 | const float* wo = wih + static_cast<std::size_t>(3 * H + n) * I; | |
| 104 |
2/2✓ Branch 0 taken 109236 times.
✓ Branch 1 taken 36054 times.
|
145290 | for (int j = 0; j < I; ++j) { |
| 105 | 109236 | const float xj = x[j]; | |
| 106 | 109236 | zi += wi[j] * xj; zf += wf[j] * xj; zg += wg[j] * xj; zo += wo[j] * xj; | |
| 107 | 109236 | } | |
| 108 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 36026 times.
|
36054 | if (hprev) { |
| 109 | 36026 | const float* ui = whh + static_cast<std::size_t>(n) * H; | |
| 110 | 36026 | const float* uf = whh + static_cast<std::size_t>(H + n) * H; | |
| 111 | 36026 | const float* ug = whh + static_cast<std::size_t>(2 * H + n) * H; | |
| 112 | 36026 | const float* uo = whh + static_cast<std::size_t>(3 * H + n) * H; | |
| 113 |
2/2✓ Branch 0 taken 180534 times.
✓ Branch 1 taken 36026 times.
|
216560 | for (int m = 0; m < H; ++m) { |
| 114 | 180534 | const float hm = hprev[m]; | |
| 115 | 180534 | zi += ui[m] * hm; zf += uf[m] * hm; zg += ug[m] * hm; zo += uo[m] * hm; | |
| 116 | 180534 | } | |
| 117 | 36026 | } | |
| 118 | |||
| 119 | 36054 | const float ig = sigmoidf(zi); | |
| 120 | 36054 | const float fg = sigmoidf(zf); | |
| 121 | 36054 | const float gg = std::tanh(zg); | |
| 122 | 36054 | const float og = sigmoidf(zo); | |
| 123 |
2/2✓ Branch 0 taken 36026 times.
✓ Branch 1 taken 28 times.
|
36054 | const float cprev_n = cprev ? cprev[n] : 0.0f; |
| 124 | 36054 | const float cn = fg * cprev_n + ig * gg; | |
| 125 | |||
| 126 | 36054 | grow[n] = ig; grow[H + n] = fg; grow[2 * H + n] = gg; grow[3 * H + n] = og; | |
| 127 | 36054 | crow[n] = cn; | |
| 128 | 36054 | hrow[n] = og * std::tanh(cn); | |
| 129 | 36054 | } | |
| 130 | 7200 | } | |
| 131 | 3598 | } | |
| 132 | |||
| 133 |
2/2✓ Branch 0 taken 895 times.
✓ Branch 1 taken 6 times.
|
901 | if (hT) { |
| 134 | 6 | float* p = hT->host_f32_mut(); | |
| 135 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
|
18 | for (int b = 0; b < B; ++b) |
| 136 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 12 times.
|
77 | for (int n = 0; n < H; ++n) |
| 137 | 65 | p[static_cast<std::size_t>(b) * H + n] = | |
| 138 | 77 | yp[(static_cast<std::size_t>(T - 1) * B + b) * H + n]; | |
| 139 | 6 | } | |
| 140 |
2/2✓ Branch 0 taken 895 times.
✓ Branch 1 taken 6 times.
|
901 | if (cT) { |
| 141 | 6 | float* p = cT->host_f32_mut(); | |
| 142 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
|
18 | for (int b = 0; b < B; ++b) |
| 143 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 12 times.
|
77 | for (int n = 0; n < H; ++n) |
| 144 | 65 | p[static_cast<std::size_t>(b) * H + n] = | |
| 145 | 77 | cp[(static_cast<std::size_t>(T - 1) * B + b) * H + n]; | |
| 146 | 6 | } | |
| 147 | 901 | } | |
| 148 | |||
| 149 | 407 | void lstm_backward(const ::brotensor::Tensor& X, const ::brotensor::Tensor& W_ih, | |
| 150 | const ::brotensor::Tensor& W_hh, | ||
| 151 | const ::brotensor::Tensor* h0, const ::brotensor::Tensor* c0, | ||
| 152 | const ::brotensor::Tensor& Y, const ::brotensor::Tensor& gates, | ||
| 153 | const ::brotensor::Tensor& C, | ||
| 154 | const ::brotensor::Tensor& dY, int T, int B, | ||
| 155 | ::brotensor::Tensor& dX, ::brotensor::Tensor& dW_ih, | ||
| 156 | ::brotensor::Tensor& dW_hh, | ||
| 157 | ::brotensor::Tensor* db_ih, ::brotensor::Tensor* db_hh, | ||
| 158 | ::brotensor::Tensor* dh0, ::brotensor::Tensor* dc0) { | ||
| 159 | 407 | require_cpu_f32(X, "X"); | |
| 160 | 407 | require_cpu_f32(W_ih, "W_ih"); | |
| 161 | 407 | require_cpu_f32(W_hh, "W_hh"); | |
| 162 | 407 | require_cpu_f32(Y, "Y"); | |
| 163 | 407 | require_cpu_f32(gates, "gates"); | |
| 164 | 407 | require_cpu_f32(C, "C"); | |
| 165 | 407 | require_cpu_f32(dY, "dY"); | |
| 166 | 407 | const int H = W_hh.cols; | |
| 167 | 407 | const int I = W_ih.cols; | |
| 168 | 407 | const int G = 4 * H; | |
| 169 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (T <= 0 || B <= 0) |
| 170 | ✗ | throw std::runtime_error("lstm_backward: T and B must be > 0"); | |
| 171 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (W_ih.rows != G || W_hh.rows != G) |
| 172 | ✗ | throw std::runtime_error("lstm_backward: W_ih/W_hh must have 4*H rows"); | |
| 173 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (X.rows != T * B || X.cols != I) |
| 174 | ✗ | throw std::runtime_error("lstm_backward: X must be (T*B, I)"); | |
| 175 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (dY.rows != T * B || dY.cols != H) |
| 176 | ✗ | throw std::runtime_error("lstm_backward: dY must be (T*B, H)"); | |
| 177 | |||
| 178 | // Parameter grads accumulate — require pre-sized buffers, do NOT zero them. | ||
| 179 | 407 | require_cpu_f32(dW_ih, "dW_ih"); | |
| 180 | 407 | require_cpu_f32(dW_hh, "dW_hh"); | |
| 181 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (dW_ih.rows != G || dW_ih.cols != I) |
| 182 | ✗ | throw std::runtime_error("lstm_backward: dW_ih must be (4H, I), zeroed"); | |
| 183 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | if (dW_hh.rows != G || dW_hh.cols != H) |
| 184 | ✗ | throw std::runtime_error("lstm_backward: dW_hh must be (4H, H), zeroed"); | |
| 185 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 407 times.
✓ Branch 2 taken 407 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
407 | if (db_ih) { require_cpu_f32(*db_ih, "db_ih"); if (db_ih->rows != G || db_ih->cols != 1) throw std::runtime_error("lstm_backward: db_ih must be (4H, 1), zeroed"); } |
| 186 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 407 times.
✓ Branch 2 taken 407 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
407 | if (db_hh) { require_cpu_f32(*db_hh, "db_hh"); if (db_hh->rows != G || db_hh->cols != 1) throw std::runtime_error("lstm_backward: db_hh must be (4H, 1), zeroed"); } |
| 187 | |||
| 188 | 407 | const float* h0p = opt_ptr(h0, "h0", B, H); | |
| 189 | 407 | const float* c0p = opt_ptr(c0, "c0", B, H); | |
| 190 | |||
| 191 | 407 | dX.resize(T * B, I); | |
| 192 |
2/2✓ Branch 0 taken 400 times.
✓ Branch 1 taken 7 times.
|
407 | if (dh0) dh0->resize(B, H); |
| 193 |
2/2✓ Branch 0 taken 400 times.
✓ Branch 1 taken 7 times.
|
407 | if (dc0) dc0->resize(B, H); |
| 194 | |||
| 195 | 407 | const float* xp = X.host_f32(); | |
| 196 | 407 | const float* yp = Y.host_f32(); | |
| 197 | 407 | const float* gp = gates.host_f32(); | |
| 198 | 407 | const float* cp = C.host_f32(); | |
| 199 | 407 | const float* dyp = dY.host_f32(); | |
| 200 | 407 | const float* wih = W_ih.host_f32(); | |
| 201 | 407 | const float* whh = W_hh.host_f32(); | |
| 202 | 407 | float* dxp = dX.host_f32_mut(); | |
| 203 | 407 | float* dwih = dW_ih.host_f32_mut(); | |
| 204 | 407 | float* dwhh = dW_hh.host_f32_mut(); | |
| 205 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | float* dbih = db_ih ? db_ih->host_f32_mut() : nullptr; |
| 206 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | float* dbhh = db_hh ? db_hh->host_f32_mut() : nullptr; |
| 207 | |||
| 208 | 407 | std::fill(dxp, dxp + static_cast<std::size_t>(T) * B * I, 0.0f); | |
| 209 | |||
| 210 | // Recurrent grad carried backward in time: dL/dh_{t} and dL/dc_{t} arriving | ||
| 211 | // from step t+1. After the loop these hold dL/dh0 and dL/dc0. | ||
| 212 | 407 | std::vector<float> dh_next(static_cast<std::size_t>(B) * H, 0.0f); | |
| 213 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | std::vector<float> dc_next(static_cast<std::size_t>(B) * H, 0.0f); |
| 214 |
1/2✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
|
407 | std::vector<float> dh_new(static_cast<std::size_t>(H)); |
| 215 | |||
| 216 |
2/2✓ Branch 0 taken 1625 times.
✓ Branch 1 taken 407 times.
|
2032 | for (int t = T - 1; t >= 0; --t) { |
| 217 |
2/2✓ Branch 0 taken 1625 times.
✓ Branch 1 taken 3252 times.
|
4877 | for (int b = 0; b < B; ++b) { |
| 218 | 3252 | const std::size_t row = static_cast<std::size_t>(t) * B + b; | |
| 219 | 3252 | const float* grow = gp + row * G; | |
| 220 | 3252 | const float* crow = cp + row * H; | |
| 221 | 3252 | const float* x = xp + row * I; | |
| 222 | 3252 | const float* dyrow = dyp + row * H; | |
| 223 |
4/4✓ Branch 0 taken 2438 times.
✓ Branch 1 taken 814 times.
✓ Branch 2 taken 811 times.
✓ Branch 3 taken 3 times.
|
3252 | const float* hprev = (t == 0) ? (h0p ? h0p + static_cast<std::size_t>(b) * H : nullptr) |
| 224 | 2438 | : yp + (static_cast<std::size_t>(t - 1) * B + b) * H; | |
| 225 |
4/4✓ Branch 0 taken 2438 times.
✓ Branch 1 taken 814 times.
✓ Branch 2 taken 811 times.
✓ Branch 3 taken 3 times.
|
3252 | const float* cprev = (t == 0) ? (c0p ? c0p + static_cast<std::size_t>(b) * H : nullptr) |
| 226 | 2438 | : cp + (static_cast<std::size_t>(t - 1) * B + b) * H; | |
| 227 | 3252 | float* dxrow = dxp + row * I; | |
| 228 | 3252 | float* dh_b = dh_next.data() + static_cast<std::size_t>(b) * H; | |
| 229 | 3252 | float* dc_b = dc_next.data() + static_cast<std::size_t>(b) * H; | |
| 230 | |||
| 231 |
1/2✓ Branch 0 taken 3252 times.
✗ Branch 1 not taken.
|
3252 | std::fill(dh_new.begin(), dh_new.end(), 0.0f); |
| 232 | |||
| 233 |
2/2✓ Branch 0 taken 16287 times.
✓ Branch 1 taken 3252 times.
|
19539 | for (int n = 0; n < H; ++n) { |
| 234 | 16287 | const float ig = grow[n], fg = grow[H + n], gg = grow[2 * H + n], og = grow[3 * H + n]; | |
| 235 | 16287 | const float tc = std::tanh(crow[n]); | |
| 236 | 16287 | const float dh = dyrow[n] + dh_b[n]; | |
| 237 | 16287 | const float do_ = dh * tc; | |
| 238 | 16287 | const float dc = dh * og * (1.0f - tc * tc) + dc_b[n]; | |
| 239 |
2/2✓ Branch 0 taken 16273 times.
✓ Branch 1 taken 14 times.
|
16287 | const float cprev_n = cprev ? cprev[n] : 0.0f; |
| 240 | 16287 | const float df = dc * cprev_n; | |
| 241 | 16287 | const float di = dc * gg; | |
| 242 | 16287 | const float dg = dc * ig; | |
| 243 | 16287 | const float dcprev = dc * fg; | |
| 244 | |||
| 245 | // Gate pre-activation grads (sigmoid'/tanh' on cached outputs). | ||
| 246 | 16287 | const float dzi = di * ig * (1.0f - ig); | |
| 247 | 16287 | const float dzf = df * fg * (1.0f - fg); | |
| 248 | 16287 | const float dzg = dg * (1.0f - gg * gg); | |
| 249 | 16287 | const float dzo = do_ * og * (1.0f - og); | |
| 250 | |||
| 251 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16287 times.
|
16287 | if (dbih) { dbih[n] += dzi; dbih[H + n] += dzf; dbih[2 * H + n] += dzg; dbih[3 * H + n] += dzo; } |
| 252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16287 times.
|
16287 | if (dbhh) { dbhh[n] += dzi; dbhh[H + n] += dzf; dbhh[2 * H + n] += dzg; dbhh[3 * H + n] += dzo; } |
| 253 | |||
| 254 | // Input path: dX = W_ih^T·dz ; dW_ih += dz·x^T | ||
| 255 | 16287 | const float* wi = wih + static_cast<std::size_t>(n) * I; | |
| 256 | 16287 | const float* wf = wih + static_cast<std::size_t>(H + n) * I; | |
| 257 | 16287 | const float* wg = wih + static_cast<std::size_t>(2 * H + n) * I; | |
| 258 | 16287 | const float* wo = wih + static_cast<std::size_t>(3 * H + n) * I; | |
| 259 | 16287 | float* dwi = dwih + static_cast<std::size_t>(n) * I; | |
| 260 | 16287 | float* dwf = dwih + static_cast<std::size_t>(H + n) * I; | |
| 261 | 16287 | float* dwg = dwih + static_cast<std::size_t>(2 * H + n) * I; | |
| 262 | 16287 | float* dwo = dwih + static_cast<std::size_t>(3 * H + n) * I; | |
| 263 |
2/2✓ Branch 0 taken 49398 times.
✓ Branch 1 taken 16287 times.
|
65685 | for (int j = 0; j < I; ++j) { |
| 264 | 49398 | dxrow[j] += wi[j] * dzi + wf[j] * dzf + wg[j] * dzg + wo[j] * dzo; | |
| 265 | 49398 | const float xj = x[j]; | |
| 266 | 49398 | dwi[j] += dzi * xj; dwf[j] += dzf * xj; dwg[j] += dzg * xj; dwo[j] += dzo * xj; | |
| 267 | 49398 | } | |
| 268 | |||
| 269 | // Recurrent path: dh_prev = W_hh^T·dz ; dW_hh += dz·h_prev^T | ||
| 270 | 16287 | const float* ui = whh + static_cast<std::size_t>(n) * H; | |
| 271 | 16287 | const float* uf = whh + static_cast<std::size_t>(H + n) * H; | |
| 272 | 16287 | const float* ug = whh + static_cast<std::size_t>(2 * H + n) * H; | |
| 273 | 16287 | const float* uo = whh + static_cast<std::size_t>(3 * H + n) * H; | |
| 274 | 16287 | float* dui = dwhh + static_cast<std::size_t>(n) * H; | |
| 275 | 16287 | float* duf = dwhh + static_cast<std::size_t>(H + n) * H; | |
| 276 | 16287 | float* dug = dwhh + static_cast<std::size_t>(2 * H + n) * H; | |
| 277 | 16287 | float* duo = dwhh + static_cast<std::size_t>(3 * H + n) * H; | |
| 278 |
2/2✓ Branch 0 taken 81633 times.
✓ Branch 1 taken 16287 times.
|
97920 | for (int m = 0; m < H; ++m) { |
| 279 | 81633 | dh_new[m] += ui[m] * dzi + uf[m] * dzf + ug[m] * dzg + uo[m] * dzo; | |
| 280 |
2/2✓ Branch 0 taken 66 times.
✓ Branch 1 taken 81567 times.
|
81633 | if (hprev) { |
| 281 | 81567 | const float hm = hprev[m]; | |
| 282 | 81567 | dui[m] += dzi * hm; duf[m] += dzf * hm; dug[m] += dzg * hm; duo[m] += dzo * hm; | |
| 283 | 81567 | } | |
| 284 | 81633 | } | |
| 285 | |||
| 286 | // dL/dc_{t-1} for this unit (cell carry is per-unit, no coupling). | ||
| 287 | 16287 | dc_b[n] = dcprev; | |
| 288 | 16287 | } | |
| 289 | |||
| 290 | // Swap in the freshly accumulated dL/dh_{t-1} for the earlier step. | ||
| 291 |
2/2✓ Branch 0 taken 16287 times.
✓ Branch 1 taken 3252 times.
|
19539 | for (int m = 0; m < H; ++m) dh_b[m] = dh_new[m]; |
| 292 | 3252 | } | |
| 293 | 1625 | } | |
| 294 | |||
| 295 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 400 times.
|
407 | if (dh0) { |
| 296 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | float* p = dh0->host_f32_mut(); |
| 297 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 7 times.
|
82 | for (std::size_t i = 0; i < dh_next.size(); ++i) p[i] = dh_next[i]; |
| 298 | 7 | } | |
| 299 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 400 times.
|
407 | if (dc0) { |
| 300 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | float* p = dc0->host_f32_mut(); |
| 301 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 7 times.
|
82 | for (std::size_t i = 0; i < dc_next.size(); ++i) p[i] = dc_next[i]; |
| 302 | 7 | } | |
| 303 | 407 | } | |
| 304 | |||
| 305 | } // namespace brotensor::detail::cpu | ||
| 306 |