small_blas.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 Google Inc. All rights reserved.
  3. // http://ceres-solver.org/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Google Inc. nor the names of its contributors may be
  14. // used to endorse or promote products derived from this software without
  15. // specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. // POSSIBILITY OF SUCH DAMAGE.
  28. //
  29. // Author: sameeragarwal@google.com (Sameer Agarwal)
  30. //
  31. // Simple blas functions for use in the Schur Eliminator. These are
  32. // fairly basic implementations which already yield a significant
  33. // speedup in the eliminator performance.
  34. #ifndef CERES_INTERNAL_SMALL_BLAS_H_
  35. #define CERES_INTERNAL_SMALL_BLAS_H_
  36. #include "ceres/internal/eigen.h"
  37. #include "ceres/internal/export.h"
  38. #include "glog/logging.h"
  39. #include "small_blas_generic.h"
  40. namespace ceres::internal {
  41. // The following three macros are used to share code and reduce
  42. // template junk across the various GEMM variants.
  43. #define CERES_GEMM_BEGIN(name) \
  44. template <int kRowA, int kColA, int kRowB, int kColB, int kOperation> \
  45. inline void name(const double* A, \
  46. const int num_row_a, \
  47. const int num_col_a, \
  48. const double* B, \
  49. const int num_row_b, \
  50. const int num_col_b, \
  51. double* C, \
  52. const int start_row_c, \
  53. const int start_col_c, \
  54. const int row_stride_c, \
  55. const int col_stride_c)
  56. #define CERES_GEMM_NAIVE_HEADER \
  57. DCHECK_GT(num_row_a, 0); \
  58. DCHECK_GT(num_col_a, 0); \
  59. DCHECK_GT(num_row_b, 0); \
  60. DCHECK_GT(num_col_b, 0); \
  61. DCHECK_GE(start_row_c, 0); \
  62. DCHECK_GE(start_col_c, 0); \
  63. DCHECK_GT(row_stride_c, 0); \
  64. DCHECK_GT(col_stride_c, 0); \
  65. DCHECK((kRowA == Eigen::Dynamic) || (kRowA == num_row_a)); \
  66. DCHECK((kColA == Eigen::Dynamic) || (kColA == num_col_a)); \
  67. DCHECK((kRowB == Eigen::Dynamic) || (kRowB == num_row_b)); \
  68. DCHECK((kColB == Eigen::Dynamic) || (kColB == num_col_b)); \
  69. const int NUM_ROW_A = (kRowA != Eigen::Dynamic ? kRowA : num_row_a); \
  70. const int NUM_COL_A = (kColA != Eigen::Dynamic ? kColA : num_col_a); \
  71. const int NUM_ROW_B = (kRowB != Eigen::Dynamic ? kRowB : num_row_b); \
  72. const int NUM_COL_B = (kColB != Eigen::Dynamic ? kColB : num_col_b);
  73. #define CERES_GEMM_EIGEN_HEADER \
  74. const typename EigenTypes<kRowA, kColA>::ConstMatrixRef Aref( \
  75. A, num_row_a, num_col_a); \
  76. const typename EigenTypes<kRowB, kColB>::ConstMatrixRef Bref( \
  77. B, num_row_b, num_col_b); \
  78. MatrixRef Cref(C, row_stride_c, col_stride_c);
  79. // clang-format off
  80. #define CERES_CALL_GEMM(name) \
  81. name<kRowA, kColA, kRowB, kColB, kOperation>( \
  82. A, num_row_a, num_col_a, \
  83. B, num_row_b, num_col_b, \
  84. C, start_row_c, start_col_c, row_stride_c, col_stride_c);
  85. // clang-format on
  86. #define CERES_GEMM_STORE_SINGLE(p, index, value) \
  87. if (kOperation > 0) { \
  88. p[index] += value; \
  89. } else if (kOperation < 0) { \
  90. p[index] -= value; \
  91. } else { \
  92. p[index] = value; \
  93. }
  94. #define CERES_GEMM_STORE_PAIR(p, index, v1, v2) \
  95. if (kOperation > 0) { \
  96. p[index] += v1; \
  97. p[index + 1] += v2; \
  98. } else if (kOperation < 0) { \
  99. p[index] -= v1; \
  100. p[index + 1] -= v2; \
  101. } else { \
  102. p[index] = v1; \
  103. p[index + 1] = v2; \
  104. }
  105. // For the matrix-matrix functions below, there are three variants for
  106. // each functionality. Foo, FooNaive and FooEigen. Foo is the one to
  107. // be called by the user. FooNaive is a basic loop based
  108. // implementation and FooEigen uses Eigen's implementation. Foo
  109. // chooses between FooNaive and FooEigen depending on how many of the
  110. // template arguments are fixed at compile time. Currently, FooEigen
  111. // is called if all matrix dimensions are compile time
  112. // constants. FooNaive is called otherwise. This leads to the best
  113. // performance currently.
  114. //
  115. // The MatrixMatrixMultiply variants compute:
  116. //
  117. // C op A * B;
  118. //
  119. // The MatrixTransposeMatrixMultiply variants compute:
  120. //
  121. // C op A' * B
  122. //
  123. // where op can be +=, -=, or =.
  124. //
  125. // The template parameters (kRowA, kColA, kRowB, kColB) allow
  126. // specialization of the loop at compile time. If this information is
  127. // not available, then Eigen::Dynamic should be used as the template
  128. // argument.
  129. //
  130. // kOperation = 1 -> C += A * B
  131. // kOperation = -1 -> C -= A * B
  132. // kOperation = 0 -> C = A * B
  133. //
  134. // The functions can write into matrices C which are larger than the
  135. // matrix A * B. This is done by specifying the true size of C via
  136. // row_stride_c and col_stride_c, and then indicating where A * B
  137. // should be written into by start_row_c and start_col_c.
  138. //
  139. // Graphically if row_stride_c = 10, col_stride_c = 12, start_row_c =
  140. // 4 and start_col_c = 5, then if A = 3x2 and B = 2x4, we get
  141. //
  142. // ------------
  143. // ------------
  144. // ------------
  145. // ------------
  146. // -----xxxx---
  147. // -----xxxx---
  148. // -----xxxx---
  149. // ------------
  150. // ------------
  151. // ------------
  152. //
  153. CERES_GEMM_BEGIN(MatrixMatrixMultiplyEigen) {
  154. CERES_GEMM_EIGEN_HEADER
  155. Eigen::Block<MatrixRef, kRowA, kColB> block(
  156. Cref, start_row_c, start_col_c, num_row_a, num_col_b);
  157. if (kOperation > 0) {
  158. block.noalias() += Aref * Bref;
  159. } else if (kOperation < 0) {
  160. block.noalias() -= Aref * Bref;
  161. } else {
  162. block.noalias() = Aref * Bref;
  163. }
  164. }
  165. CERES_GEMM_BEGIN(MatrixMatrixMultiplyNaive) {
  166. CERES_GEMM_NAIVE_HEADER
  167. DCHECK_EQ(NUM_COL_A, NUM_ROW_B);
  168. const int NUM_ROW_C = NUM_ROW_A;
  169. const int NUM_COL_C = NUM_COL_B;
  170. DCHECK_LE(start_row_c + NUM_ROW_C, row_stride_c);
  171. DCHECK_LE(start_col_c + NUM_COL_C, col_stride_c);
  172. const int span = 4;
  173. // Calculate the remainder part first.
  174. // Process the last odd column if present.
  175. if (NUM_COL_C & 1) {
  176. int col = NUM_COL_C - 1;
  177. const double* pa = &A[0];
  178. for (int row = 0; row < NUM_ROW_C; ++row, pa += NUM_COL_A) {
  179. const double* pb = &B[col];
  180. double tmp = 0.0;
  181. for (int k = 0; k < NUM_COL_A; ++k, pb += NUM_COL_B) {
  182. tmp += pa[k] * pb[0];
  183. }
  184. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  185. CERES_GEMM_STORE_SINGLE(C, index, tmp);
  186. }
  187. // Return directly for efficiency of extremely small matrix multiply.
  188. if (NUM_COL_C == 1) {
  189. return;
  190. }
  191. }
  192. // Process the couple columns in remainder if present.
  193. if (NUM_COL_C & 2) {
  194. int col = NUM_COL_C & (~(span - 1));
  195. const double* pa = &A[0];
  196. for (int row = 0; row < NUM_ROW_C; ++row, pa += NUM_COL_A) {
  197. const double* pb = &B[col];
  198. double tmp1 = 0.0, tmp2 = 0.0;
  199. for (int k = 0; k < NUM_COL_A; ++k, pb += NUM_COL_B) {
  200. double av = pa[k];
  201. tmp1 += av * pb[0];
  202. tmp2 += av * pb[1];
  203. }
  204. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  205. CERES_GEMM_STORE_PAIR(C, index, tmp1, tmp2);
  206. }
  207. // Return directly for efficiency of extremely small matrix multiply.
  208. if (NUM_COL_C < span) {
  209. return;
  210. }
  211. }
  212. // Calculate the main part with multiples of 4.
  213. int col_m = NUM_COL_C & (~(span - 1));
  214. for (int col = 0; col < col_m; col += span) {
  215. for (int row = 0; row < NUM_ROW_C; ++row) {
  216. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  217. // clang-format off
  218. MMM_mat1x4(NUM_COL_A, &A[row * NUM_COL_A],
  219. &B[col], NUM_COL_B, &C[index], kOperation);
  220. // clang-format on
  221. }
  222. }
  223. }
  224. CERES_GEMM_BEGIN(MatrixMatrixMultiply) {
  225. #ifdef CERES_NO_CUSTOM_BLAS
  226. CERES_CALL_GEMM(MatrixMatrixMultiplyEigen)
  227. return;
  228. #else
  229. if (kRowA != Eigen::Dynamic && kColA != Eigen::Dynamic &&
  230. kRowB != Eigen::Dynamic && kColB != Eigen::Dynamic) {
  231. CERES_CALL_GEMM(MatrixMatrixMultiplyEigen)
  232. } else {
  233. CERES_CALL_GEMM(MatrixMatrixMultiplyNaive)
  234. }
  235. #endif
  236. }
  237. CERES_GEMM_BEGIN(MatrixTransposeMatrixMultiplyEigen) {
  238. CERES_GEMM_EIGEN_HEADER
  239. // clang-format off
  240. Eigen::Block<MatrixRef, kColA, kColB> block(Cref,
  241. start_row_c, start_col_c,
  242. num_col_a, num_col_b);
  243. // clang-format on
  244. if (kOperation > 0) {
  245. block.noalias() += Aref.transpose() * Bref;
  246. } else if (kOperation < 0) {
  247. block.noalias() -= Aref.transpose() * Bref;
  248. } else {
  249. block.noalias() = Aref.transpose() * Bref;
  250. }
  251. }
  252. CERES_GEMM_BEGIN(MatrixTransposeMatrixMultiplyNaive) {
  253. CERES_GEMM_NAIVE_HEADER
  254. DCHECK_EQ(NUM_ROW_A, NUM_ROW_B);
  255. const int NUM_ROW_C = NUM_COL_A;
  256. const int NUM_COL_C = NUM_COL_B;
  257. DCHECK_LE(start_row_c + NUM_ROW_C, row_stride_c);
  258. DCHECK_LE(start_col_c + NUM_COL_C, col_stride_c);
  259. const int span = 4;
  260. // Process the remainder part first.
  261. // Process the last odd column if present.
  262. if (NUM_COL_C & 1) {
  263. int col = NUM_COL_C - 1;
  264. for (int row = 0; row < NUM_ROW_C; ++row) {
  265. const double* pa = &A[row];
  266. const double* pb = &B[col];
  267. double tmp = 0.0;
  268. for (int k = 0; k < NUM_ROW_A; ++k) {
  269. tmp += pa[0] * pb[0];
  270. pa += NUM_COL_A;
  271. pb += NUM_COL_B;
  272. }
  273. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  274. CERES_GEMM_STORE_SINGLE(C, index, tmp);
  275. }
  276. // Return directly for efficiency of extremely small matrix multiply.
  277. if (NUM_COL_C == 1) {
  278. return;
  279. }
  280. }
  281. // Process the couple columns in remainder if present.
  282. if (NUM_COL_C & 2) {
  283. int col = NUM_COL_C & (~(span - 1));
  284. for (int row = 0; row < NUM_ROW_C; ++row) {
  285. const double* pa = &A[row];
  286. const double* pb = &B[col];
  287. double tmp1 = 0.0, tmp2 = 0.0;
  288. for (int k = 0; k < NUM_ROW_A; ++k) {
  289. double av = *pa;
  290. tmp1 += av * pb[0];
  291. tmp2 += av * pb[1];
  292. pa += NUM_COL_A;
  293. pb += NUM_COL_B;
  294. }
  295. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  296. CERES_GEMM_STORE_PAIR(C, index, tmp1, tmp2);
  297. }
  298. // Return directly for efficiency of extremely small matrix multiply.
  299. if (NUM_COL_C < span) {
  300. return;
  301. }
  302. }
  303. // Process the main part with multiples of 4.
  304. int col_m = NUM_COL_C & (~(span - 1));
  305. for (int col = 0; col < col_m; col += span) {
  306. for (int row = 0; row < NUM_ROW_C; ++row) {
  307. const int index = (row + start_row_c) * col_stride_c + start_col_c + col;
  308. // clang-format off
  309. MTM_mat1x4(NUM_ROW_A, &A[row], NUM_COL_A,
  310. &B[col], NUM_COL_B, &C[index], kOperation);
  311. // clang-format on
  312. }
  313. }
  314. }
  315. CERES_GEMM_BEGIN(MatrixTransposeMatrixMultiply) {
  316. #ifdef CERES_NO_CUSTOM_BLAS
  317. CERES_CALL_GEMM(MatrixTransposeMatrixMultiplyEigen)
  318. return;
  319. #else
  320. if (kRowA != Eigen::Dynamic && kColA != Eigen::Dynamic &&
  321. kRowB != Eigen::Dynamic && kColB != Eigen::Dynamic) {
  322. CERES_CALL_GEMM(MatrixTransposeMatrixMultiplyEigen)
  323. } else {
  324. CERES_CALL_GEMM(MatrixTransposeMatrixMultiplyNaive)
  325. }
  326. #endif
  327. }
  328. // Matrix-Vector multiplication
  329. //
  330. // c op A * b;
  331. //
  332. // where op can be +=, -=, or =.
  333. //
  334. // The template parameters (kRowA, kColA) allow specialization of the
  335. // loop at compile time. If this information is not available, then
  336. // Eigen::Dynamic should be used as the template argument.
  337. //
  338. // kOperation = 1 -> c += A' * b
  339. // kOperation = -1 -> c -= A' * b
  340. // kOperation = 0 -> c = A' * b
  341. template <int kRowA, int kColA, int kOperation>
  342. inline void MatrixVectorMultiply(const double* A,
  343. const int num_row_a,
  344. const int num_col_a,
  345. const double* b,
  346. double* c) {
  347. #ifdef CERES_NO_CUSTOM_BLAS
  348. const typename EigenTypes<kRowA, kColA>::ConstMatrixRef Aref(
  349. A, num_row_a, num_col_a);
  350. const typename EigenTypes<kColA>::ConstVectorRef bref(b, num_col_a);
  351. typename EigenTypes<kRowA>::VectorRef cref(c, num_row_a);
  352. // lazyProduct works better than .noalias() for matrix-vector
  353. // products.
  354. if (kOperation > 0) {
  355. cref += Aref.lazyProduct(bref);
  356. } else if (kOperation < 0) {
  357. cref -= Aref.lazyProduct(bref);
  358. } else {
  359. cref = Aref.lazyProduct(bref);
  360. }
  361. #else
  362. DCHECK_GT(num_row_a, 0);
  363. DCHECK_GT(num_col_a, 0);
  364. DCHECK((kRowA == Eigen::Dynamic) || (kRowA == num_row_a));
  365. DCHECK((kColA == Eigen::Dynamic) || (kColA == num_col_a));
  366. const int NUM_ROW_A = (kRowA != Eigen::Dynamic ? kRowA : num_row_a);
  367. const int NUM_COL_A = (kColA != Eigen::Dynamic ? kColA : num_col_a);
  368. const int span = 4;
  369. // Calculate the remainder part first.
  370. // Process the last odd row if present.
  371. if (NUM_ROW_A & 1) {
  372. int row = NUM_ROW_A - 1;
  373. const double* pa = &A[row * NUM_COL_A];
  374. const double* pb = &b[0];
  375. double tmp = 0.0;
  376. for (int col = 0; col < NUM_COL_A; ++col) {
  377. tmp += (*pa++) * (*pb++);
  378. }
  379. CERES_GEMM_STORE_SINGLE(c, row, tmp);
  380. // Return directly for efficiency of extremely small matrix multiply.
  381. if (NUM_ROW_A == 1) {
  382. return;
  383. }
  384. }
  385. // Process the couple rows in remainder if present.
  386. if (NUM_ROW_A & 2) {
  387. int row = NUM_ROW_A & (~(span - 1));
  388. const double* pa1 = &A[row * NUM_COL_A];
  389. const double* pa2 = pa1 + NUM_COL_A;
  390. const double* pb = &b[0];
  391. double tmp1 = 0.0, tmp2 = 0.0;
  392. for (int col = 0; col < NUM_COL_A; ++col) {
  393. double bv = *pb++;
  394. tmp1 += *(pa1++) * bv;
  395. tmp2 += *(pa2++) * bv;
  396. }
  397. CERES_GEMM_STORE_PAIR(c, row, tmp1, tmp2);
  398. // Return directly for efficiency of extremely small matrix multiply.
  399. if (NUM_ROW_A < span) {
  400. return;
  401. }
  402. }
  403. // Calculate the main part with multiples of 4.
  404. int row_m = NUM_ROW_A & (~(span - 1));
  405. for (int row = 0; row < row_m; row += span) {
  406. // clang-format off
  407. MVM_mat4x1(NUM_COL_A, &A[row * NUM_COL_A], NUM_COL_A,
  408. &b[0], &c[row], kOperation);
  409. // clang-format on
  410. }
  411. #endif // CERES_NO_CUSTOM_BLAS
  412. }
  413. // Similar to MatrixVectorMultiply, except that A is transposed, i.e.,
  414. //
  415. // c op A' * b;
  416. template <int kRowA, int kColA, int kOperation>
  417. inline void MatrixTransposeVectorMultiply(const double* A,
  418. const int num_row_a,
  419. const int num_col_a,
  420. const double* b,
  421. double* c) {
  422. #ifdef CERES_NO_CUSTOM_BLAS
  423. const typename EigenTypes<kRowA, kColA>::ConstMatrixRef Aref(
  424. A, num_row_a, num_col_a);
  425. const typename EigenTypes<kRowA>::ConstVectorRef bref(b, num_row_a);
  426. typename EigenTypes<kColA>::VectorRef cref(c, num_col_a);
  427. // lazyProduct works better than .noalias() for matrix-vector
  428. // products.
  429. if (kOperation > 0) {
  430. cref += Aref.transpose().lazyProduct(bref);
  431. } else if (kOperation < 0) {
  432. cref -= Aref.transpose().lazyProduct(bref);
  433. } else {
  434. cref = Aref.transpose().lazyProduct(bref);
  435. }
  436. #else
  437. DCHECK_GT(num_row_a, 0);
  438. DCHECK_GT(num_col_a, 0);
  439. DCHECK((kRowA == Eigen::Dynamic) || (kRowA == num_row_a));
  440. DCHECK((kColA == Eigen::Dynamic) || (kColA == num_col_a));
  441. const int NUM_ROW_A = (kRowA != Eigen::Dynamic ? kRowA : num_row_a);
  442. const int NUM_COL_A = (kColA != Eigen::Dynamic ? kColA : num_col_a);
  443. const int span = 4;
  444. // Calculate the remainder part first.
  445. // Process the last odd column if present.
  446. if (NUM_COL_A & 1) {
  447. int row = NUM_COL_A - 1;
  448. const double* pa = &A[row];
  449. const double* pb = &b[0];
  450. double tmp = 0.0;
  451. for (int col = 0; col < NUM_ROW_A; ++col) {
  452. tmp += *pa * (*pb++);
  453. pa += NUM_COL_A;
  454. }
  455. CERES_GEMM_STORE_SINGLE(c, row, tmp);
  456. // Return directly for efficiency of extremely small matrix multiply.
  457. if (NUM_COL_A == 1) {
  458. return;
  459. }
  460. }
  461. // Process the couple columns in remainder if present.
  462. if (NUM_COL_A & 2) {
  463. int row = NUM_COL_A & (~(span - 1));
  464. const double* pa = &A[row];
  465. const double* pb = &b[0];
  466. double tmp1 = 0.0, tmp2 = 0.0;
  467. for (int col = 0; col < NUM_ROW_A; ++col) {
  468. // clang-format off
  469. double bv = *pb++;
  470. tmp1 += *(pa ) * bv;
  471. tmp2 += *(pa + 1) * bv;
  472. pa += NUM_COL_A;
  473. // clang-format on
  474. }
  475. CERES_GEMM_STORE_PAIR(c, row, tmp1, tmp2);
  476. // Return directly for efficiency of extremely small matrix multiply.
  477. if (NUM_COL_A < span) {
  478. return;
  479. }
  480. }
  481. // Calculate the main part with multiples of 4.
  482. int row_m = NUM_COL_A & (~(span - 1));
  483. for (int row = 0; row < row_m; row += span) {
  484. // clang-format off
  485. MTV_mat4x1(NUM_ROW_A, &A[row], NUM_COL_A,
  486. &b[0], &c[row], kOperation);
  487. // clang-format on
  488. }
  489. #endif // CERES_NO_CUSTOM_BLAS
  490. }
  491. #undef CERES_GEMM_BEGIN
  492. #undef CERES_GEMM_EIGEN_HEADER
  493. #undef CERES_GEMM_NAIVE_HEADER
  494. #undef CERES_CALL_GEMM
  495. #undef CERES_GEMM_STORE_SINGLE
  496. #undef CERES_GEMM_STORE_PAIR
  497. } // namespace ceres::internal
  498. #endif // CERES_INTERNAL_SMALL_BLAS_H_