GCC Code Coverage Report


Directory: brotensor/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 47.5% 248 / 0 / 522
Functions: 61.2% 30 / 0 / 49
Branches: 17.8% 228 / 0 / 1279

src/api/native_tensor_nn.cpp
Line Branch Exec Source
1 #include "native_tensor_decl.h"
2 #include "api_internal.h"
3 #include <vector>
4
5 using namespace brotensor::api;
6 using brotensor::Tensor;
7 using brotensor::Dtype;
8
9 // Operand checks come first in every body (api_internal.h "input-size
10 // contract"); accumulated outputs ("caller zeros") are checked like inputs.
11
12 namespace {
13
14 // X is (L, numHeads*headDim) with an even, positive headDim.
15 8 bool needRopeInput(const char* L, const Tensor* xt, int32_t headDim, int32_t numHeads) {
16
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!needPositive(L, {headDim, numHeads})) return false;
17
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (headDim % 2 != 0) {
18
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 setError(std::string(L) + ": headDim must be even");
19 2 return false;
20 }
21
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (static_cast<int64_t>(xt->cols) != static_cast<int64_t>(numHeads) * headDim) {
22
6/12
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
4 setError(std::string(L) + ": X has " + std::to_string(xt->cols) + " columns, numHeads*headDim is " +
23
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::to_string(static_cast<int64_t>(numHeads) * headDim));
24 2 return false;
25 }
26 4 return true;
27 8 }
28
29 // cos/sin tables: (L, headDim/2) FP32.
30 4 bool needRopeTables(const char* L, const Tensor* xt, const Tensor* ct, const Tensor* st, int32_t headDim) {
31 4 const int64_t n = elems({xt->rows, headDim / 2});
32
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 if (!needElems(L, "cosTbl", ct, n) || !needElems(L, "sinTbl", st, n)) return false;
33
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (ct->dtype != Dtype::FP32 || st->dtype != Dtype::FP32) {
34 ✗ setError(std::string(L) + ": cosTbl and sinTbl must be FP32");
35 ✗ return false;
36 }
37 2 return true;
38 4 }
39
40 // Decode attention: Q (L_q, H*hd), caches (L_max, kvH*hd), kvH | H.
41 ✗ bool needDecodeShapes(const char* L, const Tensor* qt, const Tensor* kt, const Tensor* vt,
42 int32_t numHeads, int32_t kvHeads) {
43 ✗ if (!needPositive(L, {numHeads, kvHeads})) return false;
44 ✗ if (numHeads % kvHeads != 0 || qt->cols % numHeads != 0) {
45 ✗ setError(std::string(L) + ": numKvHeads must divide numHeads, and numHeads Q.cols");
46 ✗ return false;
47 }
48 ✗ const int64_t headDim = qt->cols / numHeads;
49 ✗ if (kt->cols != kvHeads * headDim || vt->cols != kvHeads * headDim || vt->rows != kt->rows) {
50 ✗ setError(std::string(L) + ": K_cache/V_cache must both be (L_max, numKvHeads*headDim) with headDim = Q.cols/numHeads");
51 ✗ return false;
52 }
53 ✗ return needSameDtype(L, "K_cache", kt, qt) && needSameDtype(L, "V_cache", vt, qt);
54 ✗ }
55
56 // Embedding indices: B values in [0, V), read back and range-checked (an
57 // index out of range is an out-of-bounds read of the table on the device),
58 // then handed to the op as an INT32 buffer on the table's device. The index
59 // tensor is INT32 storage or FP32 values.
60 20 bool embeddingIndices(const char* L, const Tensor* it, int32_t B, int32_t V, brotensor::Device dev,
61 Tensor& deviceIdx) {
62
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
20 if (!needElems(L, "indices", it, B)) return false;
63 18 std::vector<int32_t> idx(static_cast<size_t>(B));
64
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 auto host = it->to(brotensor::Device::CPU);
65
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
18 if (it->dtype == Dtype::INT32) {
66
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::memcpy(idx.data(), host.host_raw(), idx.size() * sizeof(int32_t));
67
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
18 } else if (it->dtype == Dtype::FP32) {
68
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 const float* f = host.host_f32();
69
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
16 for (int32_t i = 0; i < B; ++i) idx[i] = static_cast<int32_t>(f[i]);
70 6 } else {
71 ✗ setError(std::string(L) + ": indices must be INT32 storage or FP32 values");
72 ✗ return false;
73 }
74
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 24 times.
38 for (int32_t i = 0; i < B; ++i) {
75
4/4
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 20 times.
24 if (idx[i] < 0 || idx[i] >= V) {
76
9/18
✓ 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.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 4 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
8 setError(std::string(L) + ": index " + std::to_string(idx[i]) + " at " + std::to_string(i) +
77
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 " is outside the table's " + std::to_string(V) + " rows");
78 4 return false;
79 }
80 20 }
81
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 deviceIdx = Tensor::from_raw_bytes_on(dev, idx.data(), B, 1, Dtype::INT32, idx.size() * sizeof(int32_t));
82 14 return true;
83 20 }
84
85 } // namespace
86
87 extern "C" {
88
89 ✗ void bro_tensor_layernormBackward(void* dY, void* xhat, void* gamma, double rstd, void* dX, void* dGamma, void* dBeta) {
90 ✗ const char* L = "layernormBackward";
91 ✗ auto* dyt = toTensor(dY);
92 ✗ auto* xht = toTensor(xhat);
93 ✗ auto* gt = toTensor(gamma);
94 ✗ auto* dxt = toTensor(dX);
95 ✗ auto* dgt = toTensor(dGamma);
96 ✗ auto* dbt = toTensor(dBeta);
97 ✗ if (!need(L, {dyt, xht, gt, dxt, dgt, dbt})) return;
98 ✗ if (!needPair(L, "xhat", xht, dyt) || !needPair(L, "gamma", gt, dyt)) return;
99 ✗ if (!needElems(L, "dGamma", dgt, dyt->size()) || !needElems(L, "dBeta", dbt, dyt->size())) return;
100 ✗ if (!needSameDtype(L, "dGamma", dgt, dyt) || !needSameDtype(L, "dBeta", dbt, dyt)) return;
101 BROTENSOR_API_TRY
102 ✗ brotensor::layernorm_backward(*dyt, *xht, *gt, static_cast<float>(rstd), *dxt, *dgt, *dbt);
103 ✗ BROTENSOR_API_CATCH(L)
104 ✗ }
105
106 ✗ void bro_tensor_layernormForwardInferenceBatched(void* X_RD, void* gamma, void* beta, void* Y_RD, double eps) {
107 ✗ const char* L = "layernormForwardInferenceBatched";
108 ✗ auto* xt = toTensor(X_RD);
109 ✗ auto* gt = toTensor(gamma);
110 ✗ auto* bt = toTensor(beta);
111 ✗ auto* yt = toTensor(Y_RD);
112 ✗ if (!need(L, {xt, gt, bt, yt})) return;
113 ✗ if (!needElems(L, "gamma", gt, xt->cols) || !needElems(L, "beta", bt, xt->cols)) return;
114 ✗ if (!needSameDtype(L, "gamma", gt, xt) || !needSameDtype(L, "beta", bt, xt)) return;
115 BROTENSOR_API_TRY
116 ✗ brotensor::layernorm_forward_inference_batched(*xt, *gt, *bt, *yt, static_cast<float>(eps));
117 ✗ BROTENSOR_API_CATCH(L)
118 ✗ }
119
120 ✗ void bro_tensor_layernormForwardInferenceBatchedFp16(void* X_RD, void* gamma, void* beta, void* Y_RD, double eps) {
121 ✗ const char* L = "layernormForwardInferenceBatchedFp16";
122 ✗ auto* xt = toTensor(X_RD);
123 ✗ auto* gt = toTensor(gamma);
124 ✗ auto* bt = toTensor(beta);
125 ✗ auto* yt = toTensor(Y_RD);
126 ✗ if (!need(L, {xt, gt, bt, yt})) return;
127 ✗ if (!needElems(L, "gamma", gt, xt->cols) || !needElems(L, "beta", bt, xt->cols)) return;
128 ✗ if (!needSameDtype(L, "gamma", gt, xt) || !needSameDtype(L, "beta", bt, xt)) return;
129 BROTENSOR_API_TRY
130 ✗ brotensor::layernorm_forward_inference_batched_fp16(*xt, *gt, *bt, *yt, static_cast<float>(eps));
131 ✗ BROTENSOR_API_CATCH(L)
132 ✗ }
133
134 2 void bro_tensor_rmsNormForward(void* X, void* gamma, double eps, void* Y) {
135 2 const char* L = "rmsNormForward";
136 2 auto* xt = toTensor(X);
137 2 auto* gt = toTensor(gamma);
138 2 auto* yt = toTensor(Y);
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need(L, {xt, gt, yt})) return;
140 // gamma matches X or is FP32 against 16-bit activations.
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!needElems(L, "gamma", gt, xt->cols)) return;
142 ✗ if (gt->dtype != Dtype::FP32 && !needSameDtype(L, "gamma", gt, xt)) return;
143 BROTENSOR_API_TRY
144 ✗ brotensor::rms_norm_forward(*xt, *gt, static_cast<float>(eps), *yt);
145 ✗ BROTENSOR_API_CATCH(L)
146 2 }
147
148 ✗ void bro_tensor_rmsNormBackward(void* X, void* gamma, void* dY, double eps, void* dX, void* dGamma) {
149 ✗ const char* L = "rmsNormBackward";
150 ✗ auto* xt = toTensor(X);
151 ✗ auto* gt = toTensor(gamma);
152 ✗ auto* dyt = toTensor(dY);
153 ✗ auto* dxt = toTensor(dX);
154 ✗ auto* dgt = toTensor(dGamma);
155 ✗ if (!need(L, {xt, gt, dyt, dxt, dgt})) return;
156 ✗ if (!needElems(L, "gamma", gt, xt->cols) || !needSameDtype(L, "gamma", gt, xt)) return;
157 ✗ if (!needPair(L, "dY", dyt, xt)) return;
158 ✗ if (!needElems(L, "dGamma", dgt, xt->cols) || !needSameDtype(L, "dGamma", dgt, xt)) return;
159 BROTENSOR_API_TRY
160 ✗ brotensor::rms_norm_backward(*xt, *gt, *dyt, static_cast<float>(eps), *dxt, *dgt);
161 ✗ BROTENSOR_API_CATCH(L)
162 ✗ }
163
164 2 void bro_tensor_groupNormForward(void* X, void* gamma, void* beta, int32_t N, int32_t C, int32_t H, int32_t W, int32_t numGroups, double eps, void* Y) {
165 2 const char* L = "groupNormForward";
166 2 auto* xt = toTensor(X);
167 2 auto* gt = toTensor(gamma);
168 2 auto* bt = toTensor(beta);
169 2 auto* yt = toTensor(Y);
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need(L, {xt, gt, bt, yt})) return;
171
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!needPositive(L, {numGroups}) || !needNonNegative(L, {N, C, H, W})) return;
172
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (C % numGroups != 0) { setError("groupNormForward: numGroups must divide C"); return; }
173 ✗ if (!needElems(L, "X", xt, elems({N, C, H, W}))) return;
174 ✗ if (!needElems(L, "gamma", gt, C) || !needElems(L, "beta", bt, C)) return;
175 ✗ if (!needSameDtype(L, "gamma", gt, xt) || !needSameDtype(L, "beta", bt, xt)) return;
176 BROTENSOR_API_TRY
177 ✗ brotensor::group_norm_forward(*xt, *gt, *bt, N, C, H, W, numGroups, static_cast<float>(eps), *yt);
178 ✗ BROTENSOR_API_CATCH(L)
179 2 }
180
181 ✗ void bro_tensor_groupNormBackward(void* X, void* gamma, void* dY, int32_t N, int32_t C, int32_t H, int32_t W, int32_t numGroups, double eps, void* dX, void* dGamma, void* dBeta) {
182 ✗ const char* L = "groupNormBackward";
183 ✗ auto* xt = toTensor(X);
184 ✗ auto* gt = toTensor(gamma);
185 ✗ auto* dyt = toTensor(dY);
186 ✗ auto* dxt = toTensor(dX);
187 ✗ auto* dgt = toTensor(dGamma);
188 ✗ auto* dbt = toTensor(dBeta);
189 ✗ if (!need(L, {xt, gt, dyt, dxt, dgt, dbt})) return;
190 ✗ if (!needPositive(L, {numGroups}) || !needNonNegative(L, {N, C, H, W})) return;
191 ✗ if (C % numGroups != 0) { setError("groupNormBackward: numGroups must divide C"); return; }
192 ✗ const int64_t n = elems({N, C, H, W});
193 ✗ if (!needElems(L, "X", xt, n) || !needElems(L, "dY", dyt, n) || !needSameDtype(L, "dY", dyt, xt)) return;
194 ✗ for (const Tensor* t : {gt, dgt, dbt}) {
195 ✗ if (!needElems(L, "gamma/dGamma/dBeta", t, C) || !needSameDtype(L, "gamma/dGamma/dBeta", t, xt)) return;
196 }
197 BROTENSOR_API_TRY
198 ✗ brotensor::group_norm_backward(*xt, *gt, *dyt, N, C, H, W, numGroups, static_cast<float>(eps), *dxt, *dgt, *dbt);
199 ✗ BROTENSOR_API_CATCH(L)
200 ✗ }
201
202 4 void bro_tensor_ropeForward(void* X, int32_t headDim, int32_t numHeads, int32_t seqOffset, double thetaBase, void* Y) {
203 4 const char* L = "ropeForward";
204 4 auto* xt = toTensor(X);
205 4 auto* yt = toTensor(Y);
206
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (!need(L, {xt, yt}) || !needRopeInput(L, xt, headDim, numHeads)) return;
207 BROTENSOR_API_TRY
208 ✗ brotensor::rope_forward(*xt, headDim, numHeads, seqOffset, static_cast<float>(thetaBase), *yt);
209 ✗ BROTENSOR_API_CATCH(L)
210 4 }
211
212 ✗ void bro_tensor_ropeBackward(void* dY, int32_t headDim, int32_t numHeads, int32_t seqOffset, double thetaBase, void* dX) {
213 ✗ const char* L = "ropeBackward";
214 ✗ auto* dyt = toTensor(dY);
215 ✗ auto* dxt = toTensor(dX);
216 ✗ if (!need(L, {dyt, dxt}) || !needRopeInput(L, dyt, headDim, numHeads)) return;
217 BROTENSOR_API_TRY
218 ✗ brotensor::rope_backward(*dyt, headDim, numHeads, seqOffset, static_cast<float>(thetaBase), *dxt);
219 ✗ BROTENSOR_API_CATCH(L)
220 ✗ }
221
222 4 void bro_tensor_ropeApply(void* X, void* cosTbl, void* sinTbl, int32_t headDim, int32_t numHeads, void* Y) {
223 4 const char* L = "ropeApply";
224 4 auto* xt = toTensor(X);
225 4 auto* ct = toTensor(cosTbl);
226 4 auto* st = toTensor(sinTbl);
227 4 auto* yt = toTensor(Y);
228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!need(L, {xt, ct, st, yt})) return;
229
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (!needRopeInput(L, xt, headDim, numHeads) || !needRopeTables(L, xt, ct, st, headDim)) return;
230 BROTENSOR_API_TRY
231
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 brotensor::rope_apply(*xt, *ct, *st, headDim, numHeads, *yt);
232
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH(L)
233 4 }
234
235 ✗ void bro_tensor_ropeApplyBackward(void* dY, void* cosTbl, void* sinTbl, int32_t headDim, int32_t numHeads, void* dX) {
236 ✗ const char* L = "ropeApplyBackward";
237 ✗ auto* dyt = toTensor(dY);
238 ✗ auto* ct = toTensor(cosTbl);
239 ✗ auto* st = toTensor(sinTbl);
240 ✗ auto* dxt = toTensor(dX);
241 ✗ if (!need(L, {dyt, ct, st, dxt})) return;
242 ✗ if (!needRopeInput(L, dyt, headDim, numHeads) || !needRopeTables(L, dyt, ct, st, headDim)) return;
243 BROTENSOR_API_TRY
244 ✗ brotensor::rope_apply_backward(*dyt, *ct, *st, headDim, numHeads, *dxt);
245 ✗ BROTENSOR_API_CATCH(L)
246 ✗ }
247
248 ✗ void bro_tensor_modulate(void* X, void* scale, void* shift, void* Y) {
249 ✗ const char* L = "modulate";
250 ✗ auto* xt = toTensor(X);
251 ✗ auto* st = toTensor(scale);
252 ✗ auto* sht = toTensor(shift);
253 ✗ auto* yt = toTensor(Y);
254 ✗ if (!need(L, {xt, st, sht, yt})) return;
255 ✗ if (!needElems(L, "scale", st, xt->cols) || !needElems(L, "shift", sht, xt->cols)) return;
256 ✗ if (!needSameDtype(L, "scale", st, xt) || !needSameDtype(L, "shift", sht, xt)) return;
257 BROTENSOR_API_TRY
258 ✗ brotensor::modulate(*xt, *st, *sht, *yt);
259 ✗ BROTENSOR_API_CATCH(L)
260 ✗ }
261
262 ✗ void bro_tensor_broadcastMul(void* X, void* v, void* Y) {
263 ✗ const char* L = "broadcastMul";
264 ✗ auto* xt = toTensor(X);
265 ✗ auto* vt = toTensor(v);
266 ✗ auto* yt = toTensor(Y);
267 ✗ if (!need(L, {xt, vt, yt})) return;
268 ✗ if (!needElems(L, "v", vt, xt->cols) || !needSameDtype(L, "v", vt, xt)) return;
269 BROTENSOR_API_TRY
270 ✗ brotensor::broadcast_mul(*xt, *vt, *yt);
271 ✗ BROTENSOR_API_CATCH(L)
272 ✗ }
273
274 ✗ void bro_tensor_attentionTokenMoments(void* Attn, int32_t h_lat, int32_t w_lat, void* mass, void* centroid) {
275 ✗ const char* L = "attentionTokenMoments";
276 ✗ auto* at = toTensor(Attn);
277 ✗ auto* mt = toTensor(mass);
278 ✗ auto* ct = toTensor(centroid);
279 ✗ if (!need(L, {at, mt, ct}) || !needPositive(L, {h_lat, w_lat})) return;
280 ✗ if (static_cast<int64_t>(at->rows) != static_cast<int64_t>(h_lat) * w_lat) {
281 ✗ setError("attentionTokenMoments: Attn.rows must equal h_lat*w_lat");
282 ✗ return;
283 }
284 BROTENSOR_API_TRY
285 ✗ brotensor::attention_token_moments(*at, h_lat, w_lat, *mt, *ct);
286 ✗ BROTENSOR_API_CATCH(L)
287 ✗ }
288
289 // mask[k] = x[offset + k*stride] > 0.5: the last read is offset+(K-1)*stride.
290 ✗ void bro_tensor_buildSlotMask(void* x, int32_t offset, int32_t K, int32_t stride, void* mask) {
291 ✗ const char* L = "buildSlotMask";
292 ✗ auto* xt = toTensor(x);
293 ✗ auto* mt = toTensor(mask);
294 ✗ if (!need(L, {xt, mt}) || !needNonNegative(L, {offset, K, stride})) return;
295 ✗ if (K > 0 && !needElems(L, "x", xt, static_cast<int64_t>(offset) + static_cast<int64_t>(K - 1) * stride + 1)) return;
296 ✗ if (xt->dtype != Dtype::FP32) { setError("buildSlotMask: x must be FP32"); return; }
297 BROTENSOR_API_TRY
298 ✗ brotensor::build_slot_mask(*xt, offset, K, stride, *mt);
299 ✗ BROTENSOR_API_CATCH(L)
300 ✗ }
301
302 ✗ void bro_tensor_buildCausalMaskRow(int32_t L_, int32_t q, void* mask) {
303 ✗ const char* L = "buildCausalMaskRow";
304 ✗ auto* mt = toTensor(mask);
305 ✗ if (!need(L, {mt}) || !needNonNegative(L, {L_})) return;
306 BROTENSOR_API_TRY
307 ✗ brotensor::build_causal_mask_row(L_, q, *mt);
308 ✗ BROTENSOR_API_CATCH(L)
309 ✗ }
310
311 ✗ void bro_tensor_flashAttentionDecode(void* Q, void* K_cache, void* V_cache, int32_t validLen, int32_t numHeads, void* O, bool numKvHeads_given, int32_t numKvHeads, double attnSoftcap, int32_t window) {
312 ✗ const char* L = "flashAttentionDecode";
313 ✗ auto* qt = toTensor(Q);
314 ✗ auto* kt = toTensor(K_cache);
315 ✗ auto* vt = toTensor(V_cache);
316 ✗ auto* ot = toTensor(O);
317 ✗ if (!need(L, {qt, kt, vt, ot})) return;
318 ✗ const int kvHeads = (numKvHeads_given && numKvHeads > 0) ? numKvHeads : numHeads;
319 ✗ if (!needDecodeShapes(L, qt, kt, vt, numHeads, kvHeads)) return;
320 ✗ if (validLen < qt->rows || validLen > kt->rows) {
321 ✗ setError("flashAttentionDecode: validLen must lie in [Q.rows, K_cache.rows]");
322 ✗ return;
323 }
324 BROTENSOR_API_TRY
325 ✗ brotensor::flash_attention_decode(*qt, *kt, *vt, validLen, numHeads, kvHeads, *ot, static_cast<float>(attnSoftcap), window);
326 ✗ BROTENSOR_API_CATCH(L)
327 ✗ }
328
329 ✗ void bro_tensor_flashAttentionDecodeMasked(void* Q, void* K_cache, void* V_cache, void* dMask, int32_t numHeads, void* O, bool numKvHeads_given, int32_t numKvHeads, double attnSoftcap, int32_t window) {
330 ✗ const char* L = "flashAttentionDecodeMasked";
331 ✗ auto* qt = toTensor(Q);
332 ✗ auto* kt = toTensor(K_cache);
333 ✗ auto* vt = toTensor(V_cache);
334 ✗ auto* mt = toTensor(dMask);
335 ✗ auto* ot = toTensor(O);
336 // The op reads a length-L_max mask and must not be handed null.
337 ✗ if (!need(L, {qt, kt, vt, mt, ot})) return;
338 ✗ const int kvHeads = (numKvHeads_given && numKvHeads > 0) ? numKvHeads : numHeads;
339 ✗ if (!needDecodeShapes(L, qt, kt, vt, numHeads, kvHeads)) return;
340 ✗ if (qt->rows != 1) { setError("flashAttentionDecodeMasked: Q must be a single row"); return; }
341 ✗ if (!needMask(L, mt, kt->rows)) return;
342 BROTENSOR_API_TRY
343 ✗ brotensor::flash_attention_decode_masked(*qt, *kt, *vt, static_cast<const float*>(mt->data), numHeads, kvHeads,
344 ✗ *ot, static_cast<float>(attnSoftcap), window);
345 ✗ BROTENSOR_API_CATCH(L)
346 ✗ }
347
348 ✗ void bro_tensor_kvCacheAppend(void* K_new, void* V_new, int32_t curLen, void* K_cache, void* V_cache) {
349 ✗ const char* L = "kvCacheAppend";
350 ✗ auto* knt = toTensor(K_new);
351 ✗ auto* vnt = toTensor(V_new);
352 ✗ auto* kct = toTensor(K_cache);
353 ✗ auto* vct = toTensor(V_cache);
354 ✗ if (!need(L, {knt, vnt, kct, vct}) || !needNonNegative(L, {curLen})) return;
355 ✗ if (vnt->rows != knt->rows || vnt->cols != knt->cols || kct->cols != knt->cols || vct->cols != knt->cols) {
356 ✗ setError("kvCacheAppend: K_new/V_new/K_cache/V_cache must share a column count, K_new and V_new a shape");
357 ✗ return;
358 }
359 ✗ const int64_t end = static_cast<int64_t>(curLen) + knt->rows;
360 ✗ if (end > kct->rows || end > vct->rows) {
361 ✗ setError("kvCacheAppend: curLen + K_new.rows exceeds the cache's rows");
362 ✗ return;
363 }
364 ✗ for (const Tensor* t : {vnt, kct, vct}) {
365 ✗ if (!needSameDtype(L, "every operand", t, knt)) return;
366 }
367 BROTENSOR_API_TRY
368 ✗ brotensor::kv_cache_append(*knt, *vnt, curLen, *kct, *vct);
369 ✗ BROTENSOR_API_CATCH(L)
370 ✗ }
371
372 // The indices are range-checked on the host and reach the op as a device
373 // INT32 buffer. (The FP32-index path used to hand a host pointer to the
374 // device kernel.)
375 20 void bro_tensor_embeddingLookupForward(void* table, void* idxAsInt32, int32_t B, void* out) {
376 20 const char* L = "embeddingLookupForward";
377 20 auto* tt = toTensor(table);
378 20 auto* it = toTensor(idxAsInt32);
379 20 auto* ot = toTensor(out);
380
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (!need(L, {tt, it, ot})) return;
381
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 if (B <= 0) { setError("embeddingLookupForward: B must be positive"); return; }
382 BROTENSOR_API_TRY
383 20 Tensor idx;
384
3/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 6 times.
20 if (!embeddingIndices(L, it, B, tt->rows, tt->device, idx)) return;
385
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 brotensor::embedding_lookup_forward(*tt, static_cast<const int32_t*>(idx.data), B, *ot);
386
2/15
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 6 times.
✓ Branch 12 taken 14 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
20 BROTENSOR_API_CATCH(L)
387 20 }
388
389 ✗ void bro_tensor_embeddingLookupBackward(void* dOut, void* idxAsInt32, int32_t B, void* dTable) {
390 ✗ const char* L = "embeddingLookupBackward";
391 ✗ auto* dot = toTensor(dOut);
392 ✗ auto* it = toTensor(idxAsInt32);
393 ✗ auto* dtt = toTensor(dTable);
394 ✗ if (!need(L, {dot, it, dtt})) return;
395 ✗ if (B <= 0) { setError("embeddingLookupBackward: B must be positive"); return; }
396 ✗ if (!needElems(L, "dOut", dot, elems({B, dtt->cols})) || !needSameDtype(L, "dOut", dot, dtt)) return;
397 BROTENSOR_API_TRY
398 ✗ Tensor idx;
399 ✗ if (!embeddingIndices(L, it, B, dtt->rows, dtt->device, idx)) return;
400 ✗ brotensor::embedding_lookup_backward(*dot, static_cast<const int32_t*>(idx.data), B, *dtt);
401 ✗ BROTENSOR_API_CATCH(L)
402 ✗ }
403
404 2 void bro_tensor_sgdStep(void* param, void* grad, void* velocity, double lr, double momentum) {
405 2 const char* L = "sgdStep";
406 2 auto* pt = toTensor(param);
407 2 auto* gt = toTensor(grad);
408 2 auto* vt = toTensor(velocity);
409
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need(L, {pt, gt, vt})) return;
410
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (!needPair(L, "grad", gt, pt) || !needPair(L, "velocity", vt, pt)) return;
411 // The kernels read every operand as float.
412 ✗ if (pt->dtype != Dtype::FP32) { setError("sgdStep: tensors must be FP32"); return; }
413 BROTENSOR_API_TRY
414 ✗ brotensor::sgd_step(*pt, *gt, *vt, static_cast<float>(lr), static_cast<float>(momentum));
415 ✗ BROTENSOR_API_CATCH(L)
416 2 }
417
418 2 void bro_tensor_adamStep(void* param, void* grad, void* m, void* v, double lr, double beta1, double beta2, double eps, int32_t step) {
419 2 const char* L = "adamStep";
420 2 auto* pt = toTensor(param);
421 2 auto* gt = toTensor(grad);
422 2 auto* mt = toTensor(m);
423 2 auto* vt = toTensor(v);
424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need(L, {pt, gt, mt, vt})) return;
425
2/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (!needPair(L, "grad", gt, pt) || !needPair(L, "m", mt, pt) || !needPair(L, "v", vt, pt)) return;
426 ✗ if (step < 1) { setError("adamStep: step is the 1-based bias-correction counter"); return; }
427 ✗ if (pt->dtype != Dtype::FP32) { setError("adamStep: tensors must be FP32"); return; }
428 BROTENSOR_API_TRY
429 ✗ brotensor::adam_step(*pt, *gt, *mt, *vt, static_cast<float>(lr), static_cast<float>(beta1), static_cast<float>(beta2), static_cast<float>(eps), step);
430 ✗ BROTENSOR_API_CATCH(L)
431 2 }
432
433 // ---- restored from the QuickJS binding (tensor_bindings.cpp) ---------------
434
435 110 void bro_tensor_cast(void* src, void* dst, const char* outDtype) {
436
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110 times.
110 if (!need("cast", {src, dst})) return;
437 BROTENSOR_API_TRY
438
4/8
✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 110 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 110 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 110 times.
✗ Branch 7 not taken.
110 brotensor::cast(*toTensor(src), *toTensor(dst), parseDtype(outDtype));
439
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
110 BROTENSOR_API_CATCH("cast")
440 110 }
441
442 // softmaxForward(logits, probs, mask|null): the old third argument.
443 6 void bro_tensor_softmaxForwardMasked(void* logits, void* probs, uint64_t mask_bits) {
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!need("softmaxForward", {logits, probs})) return;
445
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!needMask("softmaxForward", tensorFromValue(mask_bits), toTensor(logits)->size())) return;
446 BROTENSOR_API_TRY
447
4/8
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
6 brotensor::softmax_forward(*toTensor(logits), *toTensor(probs), maskPtr(mask_bits));
448
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
6 BROTENSOR_API_CATCH("softmaxForward")
449 6 }
450
451 // layernormForward(x, gamma, beta, y, xhat, eps) -> {mean, rstd}: the two
452 // scalar caches come back as a length-2 f64[] the wrapper unpacks.
453 2 void bro_tensor_layernormForward(void* x, void* gamma, void* beta, void* y, void* xhat, double eps, bronze_native_buffer* out) {
454 static thread_local double stats[2];
455
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!out) return;
456 2 out->data = nullptr;
457 2 out->length = 0;
458 2 out->release = nullptr;
459 2 out->ctx = nullptr;
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need("layernormForward", {x, gamma, beta, y, xhat})) return;
461
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!needPair("layernormForward", "gamma", toTensor(gamma), toTensor(x)) ||
462 2 !needPair("layernormForward", "beta", toTensor(beta), toTensor(x))) return;
463 BROTENSOR_API_TRY
464 2 float mean = 0.f, rstd = 0.f;
465
6/12
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
2 brotensor::layernorm_forward(*toTensor(x), *toTensor(gamma), *toTensor(beta), *toTensor(y), *toTensor(xhat),
466 2 mean, rstd, static_cast<float>(eps));
467 2 stats[0] = mean;
468 2 stats[1] = rstd;
469 2 out->data = stats;
470 2 out->length = 2;
471
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH("layernormForward")
472 2 }
473
474 4 void bro_tensor_maskedMeanPoolForward(void* X, uint64_t mask_bits, void* y) {
475
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!need("maskedMeanPoolForward", {X, y})) return;
476
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (!needMask("maskedMeanPoolForward", tensorFromValue(mask_bits), toTensor(X)->rows)) return;
477 BROTENSOR_API_TRY
478
4/8
✓ 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.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 brotensor::masked_mean_pool_forward(*toTensor(X), maskPtr(mask_bits), *toTensor(y));
479
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
4 BROTENSOR_API_CATCH("maskedMeanPoolForward")
480 4 }
481
482 2 void bro_tensor_maskedMeanPoolBackward(void* dY, uint64_t mask_bits, int32_t K, void* dX) {
483
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need("maskedMeanPoolBackward", {dY, dX})) return;
484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!needNonNegative("maskedMeanPoolBackward", {K})) return;
485
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!needMask("maskedMeanPoolBackward", tensorFromValue(mask_bits), K)) return;
486 BROTENSOR_API_TRY
487
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 brotensor::masked_mean_pool_backward(*toTensor(dY), maskPtr(mask_bits), K, *toTensor(dX));
488
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH("maskedMeanPoolBackward")
489 2 }
490
491 2 double bro_tensor_softmaxXentFused(void* logits, void* target, uint64_t mask_bits, void* probs, void* dLogits) {
492
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!need("softmaxXentFused", {logits, target, probs, dLogits})) return 0.0;
493
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!needPair("softmaxXentFused", "target", toTensor(target), toTensor(logits))) return 0.0;
494
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!needMask("softmaxXentFused", tensorFromValue(mask_bits), toTensor(logits)->size())) return 0.0;
495 BROTENSOR_API_TRY
496
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
4 return brotensor::softmax_xent_fused(*toTensor(logits), *toTensor(target), maskPtr(mask_bits),
497
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 *toTensor(probs), *toTensor(dLogits));
498 ✗ BROTENSOR_API_CATCH("softmaxXentFused")
499 ✗ return 0.0;
500 2 }
501
502 // The head_offsets GpuTensor is a device INT32 buffer (cumulative, n_heads+1),
503 // the old binding's convention.
504 ✗ void bro_tensor_softmaxXentFusedBatched(void* logits_BL, void* target_BL, uint64_t mask_bits, void* headOffsets, int32_t nHeads, void* probs_BL, void* dLogits_BL, void* lossPerSample) {
505 ✗ const char* L = "softmaxXentFusedBatched";
506 ✗ if (!need(L, {logits_BL, target_BL, headOffsets, probs_BL, dLogits_BL, lossPerSample})) return;
507 ✗ const Tensor* lt = toTensor(logits_BL);
508 ✗ if (!needPair(L, "target_BL", toTensor(target_BL), lt)) return;
509 ✗ if (!needMask(L, tensorFromValue(mask_bits), lt->size())) return;
510 ✗ if (!needPositive(L, {nHeads})) return;
511 // The offsets are read on the device as slice bounds into every row: read
512 // them back and check them (n_heads+1 cumulative ints within a row).
513 ✗ const Tensor* ht = toTensor(headOffsets);
514 ✗ if (ht->dtype != Dtype::INT32 || !needElems(L, "headOffsets", ht, static_cast<int64_t>(nHeads) + 1)) {
515 ✗ if (ht->dtype != Dtype::INT32) setError("softmaxXentFusedBatched: headOffsets must be INT32 storage");
516 ✗ return;
517 }
518 BROTENSOR_API_TRY
519 ✗ auto host = ht->to(brotensor::Device::CPU);
520 ✗ const auto* off = static_cast<const int32_t*>(host.host_raw());
521 ✗ for (int32_t h = 0; h <= nHeads; ++h) {
522 ✗ if (off[h] < 0 || off[h] > lt->cols || (h > 0 && off[h] < off[h - 1])) {
523 ✗ setError("softmaxXentFusedBatched: headOffsets must be non-decreasing within [0, logits.cols]");
524 ✗ return;
525 }
526 ✗ }
527 ✗ brotensor::softmax_xent_fused_batched(*toTensor(logits_BL), *toTensor(target_BL), maskPtr(mask_bits),
528 ✗ static_cast<const int*>(ht->data), nHeads,
529 ✗ *toTensor(probs_BL), *toTensor(dLogits_BL), *toTensor(lossPerSample));
530 ✗ BROTENSOR_API_CATCH(L)
531 ✗ }
532
533 } // extern "C"
534
535 namespace {
536
537 6 std::vector<const brotensor::Tensor*> asConst(const std::vector<brotensor::Tensor*>& v) {
538 6 return std::vector<const brotensor::Tensor*>(v.begin(), v.end());
539 }
540
541 4 std::vector<int> asInts(const int32_t* p, uint32_t n) {
542
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 return p ? std::vector<int>(p, p + n) : std::vector<int>();
543 }
544
545 // The concat/split kernels copy every part with the FIRST part's element size.
546 12 bool needOneDtype(const char* L, const std::vector<brotensor::Tensor*>& parts, const Tensor* ref) {
547
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 10 times.
34 for (const Tensor* p : parts) {
548
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 22 times.
24 if (!needSameDtype(L, "every part", p, ref)) return false;
549 }
550 10 return true;
551 12 }
552
553 // The RNG fills write Y as float.
554 20 bool needFp32Out(const char* L, const Tensor* y) {
555
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (y->dtype != Dtype::FP32) {
556 ✗ setError(std::string(L) + ": Y must be FP32");
557 ✗ return false;
558 }
559 20 return true;
560 20 }
561
562 } // namespace
563
564 extern "C" {
565
566 4 void bro_tensor_concatRows(uint64_t parts_bits, void* out) {
567 4 std::vector<brotensor::Tensor*> parts;
568
4/8
✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
4 if (!need("concatRows", {out}) || !readTensorArray(parts_bits, parts, "concatRows")) return;
569
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
4 if (!parts.empty() && !needOneDtype("concatRows", parts, parts[0])) return;
570 BROTENSOR_API_TRY
571
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 brotensor::concat_rows(asConst(parts), *toTensor(out));
572
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2 BROTENSOR_API_CATCH("concatRows")
573 4 }
574
575 // split_rows copies consecutive runs of `in` into each part at the part's
576 // size: the parts must fit in `in` and share its dtype.
577 4 void bro_tensor_splitRows(void* in, uint64_t parts_bits) {
578 4 std::vector<brotensor::Tensor*> parts;
579
4/8
✓ 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.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 if (!need("splitRows", {in}) || !readTensorArray(parts_bits, parts, "splitRows")) return;
580
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 const Tensor* it = toTensor(in);
581
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (!needOneDtype("splitRows", parts, it)) return;
582 4 int64_t total = 0;
583
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
12 for (const Tensor* p : parts) total += p->size();
584
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (total > static_cast<int64_t>(it->size())) {
585
7/14
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
2 setError("splitRows: the parts hold " + std::to_string(total) + " elements, in has " + std::to_string(it->size()));
586 2 return;
587 }
588 BROTENSOR_API_TRY
589
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 brotensor::split_rows(*toTensor(in), parts);
590
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2 BROTENSOR_API_CATCH("splitRows")
591 4 }
592
593 // Every part is (B, d_i) with one B.
594 4 void bro_tensor_concatBatchedRows(uint64_t parts_bits, void* out) {
595 4 std::vector<brotensor::Tensor*> parts;
596
4/8
✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
4 if (!need("concatBatchedRows", {out}) || !readTensorArray(parts_bits, parts, "concatBatchedRows")) return;
597
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (!parts.empty()) {
598
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (!needOneDtype("concatBatchedRows", parts, parts[0])) return;
599
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 for (const Tensor* p : parts) {
600
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (p->rows != parts[0]->rows) {
601
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 setError("concatBatchedRows: every part must have the same row count");
602 2 return;
603 }
604 }
605 2 }
606 BROTENSOR_API_TRY
607
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 brotensor::concat_batched_rows(asConst(parts), *toTensor(out));
608
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2 BROTENSOR_API_CATCH("concatBatchedRows")
609 4 }
610
611 // C_per_part is an i32[] view INTO THE MOVING HEAP, and readTensorArray walks
612 // the parts array with allocating getProperty/getElement calls: copy the ints
613 // out first, or the second read sees a buffer the collector has moved (the
614 // gcstress run caught exactly that).
615 2 void bro_tensor_concatNchwChannels(uint64_t parts_bits, int32_t N, int32_t H, int32_t W, const int32_t* C_per_part, uint32_t C_len, void* out) {
616 2 const std::vector<int> channels = asInts(C_per_part, C_len);
617 2 std::vector<brotensor::Tensor*> parts;
618
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 if (!need("concatNchwChannels", {out}) || !readTensorArray(parts_bits, parts, "concatNchwChannels")) return;
619 BROTENSOR_API_TRY
620
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 brotensor::concat_nchw_channels(asConst(parts), N, H, W, channels, *toTensor(out));
621
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2 BROTENSOR_API_CATCH("concatNchwChannels")
622 2 }
623
624 2 void bro_tensor_concatNchwChannelsBackward(void* dY, int32_t N, int32_t H, int32_t W, const int32_t* C_per_part, uint32_t C_len, uint64_t parts_bits) {
625 2 const std::vector<int> channels = asInts(C_per_part, C_len);
626 2 std::vector<brotensor::Tensor*> parts;
627
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 if (!need("concatNchwChannelsBackward", {dY}) || !readTensorArray(parts_bits, parts, "concatNchwChannelsBackward")) return;
628 BROTENSOR_API_TRY
629
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 brotensor::concat_nchw_channels_backward(*toTensor(dY), N, H, W, channels, parts);
630
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2 BROTENSOR_API_CATCH("concatNchwChannelsBackward")
631 2 }
632
633 // ---- counter-based RNG + init (key / counter / state: BigInt or Number) ----
634
635 2 void bro_tensor_randUniform(uint64_t key_bits, uint64_t counter_bits, void* Y) {
636
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!need("randUniform", {Y}) || !needFp32Out("randUniform", toTensor(Y))) return;
637 BROTENSOR_API_TRY
638
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 brotensor::rand_uniform(bronze::embed::toUint64(bronze::Value{key_bits}),
639
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 bronze::embed::toUint64(bronze::Value{counter_bits}), *toTensor(Y));
640
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH("randUniform")
641 2 }
642
643 12 void bro_tensor_randn(uint64_t key_bits, uint64_t counter_bits, void* Y) {
644
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (!need("randn", {Y}) || !needFp32Out("randn", toTensor(Y))) return;
645 BROTENSOR_API_TRY
646
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
24 brotensor::randn(bronze::embed::toUint64(bronze::Value{key_bits}),
647
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 bronze::embed::toUint64(bronze::Value{counter_bits}), *toTensor(Y));
648
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
12 BROTENSOR_API_CATCH("randn")
649 12 }
650
651 4 void bro_tensor_randBernoulli(double p, uint64_t key_bits, uint64_t counter_bits, void* Y) {
652
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!need("randBernoulli", {Y}) || !needFp32Out("randBernoulli", toTensor(Y))) return;
653 BROTENSOR_API_TRY
654
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 brotensor::rand_bernoulli(static_cast<float>(p), bronze::embed::toUint64(bronze::Value{key_bits}),
655
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 bronze::embed::toUint64(bronze::Value{counter_bits}), *toTensor(Y));
656
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
4 BROTENSOR_API_CATCH("randBernoulli")
657 4 }
658
659 2 void bro_tensor_randnTruncated(double lo, double hi, uint64_t key_bits, uint64_t counter_bits, void* Y) {
660
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!need("randnTruncated", {Y}) || !needFp32Out("randnTruncated", toTensor(Y))) return;
661 BROTENSOR_API_TRY
662
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 brotensor::randn_truncated(static_cast<float>(lo), static_cast<float>(hi),
663
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 bronze::embed::toUint64(bronze::Value{key_bits}),
664
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 bronze::embed::toUint64(bronze::Value{counter_bits}), *toTensor(Y));
665
0/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 BROTENSOR_API_CATCH("randnTruncated")
666 2 }
667
668 // xavierInit(W, rngState) -> advanced state. The old binding returned a
669 // BigInt; the embed API mints no BigInt, so the state comes back as a Number
670 // (exact below 2^53 — thread it through successive inits as before).
671 ✗ double bro_tensor_xavierInit(void* W, uint64_t state_bits) {
672 ✗ uint64_t state = bronze::embed::toUint64(bronze::Value{state_bits});
673 ✗ if (!need("xavierInit", {W})) return static_cast<double>(state);
674 BROTENSOR_API_TRY
675 ✗ brotensor::xavier_init(*toTensor(W), state);
676 ✗ BROTENSOR_API_CATCH("xavierInit")
677 ✗ return static_cast<double>(state);
678 ✗ }
679
680 } // extern "C"
681
682 namespace brotensor::api {
683
684 16 bool readTensorArray(uint64_t bits, std::vector<brotensor::Tensor*>& out, const char* label) {
685 namespace ev = bronze::embed;
686 16 ev::Persistent arr(bronze::Value{bits});
687 16 out.clear();
688
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
16 if (!ev::isObject(arr.get())) {
689 ✗ setError(std::string(label) + ": expected an array of GpuTensors");
690 ✗ return false;
691 }
692
4/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
16 const auto n = static_cast<uint32_t>(ev::toDouble(ev::getProperty(arr.get(), "length")));
693
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 out.reserve(n);
694
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 16 times.
48 for (uint32_t i = 0; i < n; ++i) {
695
3/6
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
32 void* d = ev::handleData(ev::getElement(arr.get(), i));
696
2/4
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 auto* t = d ? toTensor(d) : nullptr;
697
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (!t) {
698 ✗ setError(std::string(label) + ": element " + std::to_string(i) + " is not a GpuTensor");
699 ✗ return false;
700 }
701
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 out.push_back(t);
702 32 }
703 16 return true;
704 16 }
705
706 } // namespace brotensor::api
707