blas.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "blas_impl.hpp"
27 #include "code_generator.hpp"
28 #include "global_options.hpp"
29 #include "runtime/casadi_runtime.hpp"
30 
31 #include <cstring>
32 
33 namespace casadi {
34 
35 std::map<std::string, Blas::Plugin> Blas::solvers_;
36 std::vector<const Blas::Plugin*> Blas::dispatch_;
37 
38 // Default-plugin storage is split:
39 // - Blas::default_ (casadi_int): hot-path read by every L1
40 // dispatcher; integer shorthand. Defined here.
41 // - GlobalOptions::default_blas_ (std::string): user-facing canonical name.
42 // Defined in global_options.cpp.
43 // GlobalOptions::setDefaultBlas keeps both in sync; Blas::setDefault is the
44 // internal entry point that updates only the integer shorthand.
45 casadi_int Blas::default_ = 0;
46 
47 #ifdef CASADI_L1_BLAS
48 casadi_daxpy_t casadi_daxpy_hook = nullptr;
49 casadi_ddot_t casadi_ddot_hook = nullptr;
50 casadi_dscal_t casadi_dscal_hook = nullptr;
51 casadi_dnrm2_t casadi_dnrm2_hook = nullptr;
52 casadi_dasum_t casadi_dasum_hook = nullptr;
53 casadi_dcopy_t casadi_dcopy_hook = nullptr;
54 #endif // CASADI_L1_BLAS
55 
56 #ifdef CASADI_WITH_THREADSAFE_SYMBOLICS
57 std::mutex Blas::mutex_solvers_;
58 #endif // CASADI_WITH_THREADSAFE_SYMBOLICS
59 
60 const std::string Blas::infix_ = "blas";
61 
62 void Blas::reference_dgemm(int transa, int transb,
63  casadi_int m, casadi_int n, casadi_int k,
64  double alpha,
65  const double* A, casadi_int lda,
66  const double* B, casadi_int ldb,
67  double beta,
68  double* C, casadi_int ldc) {
69  const bool transa_yes = (transa == CASADI_BLAS_TRANS);
70  const bool transb_yes = (transb == CASADI_BLAS_TRANS);
71 
72  // Fast path: canonical contiguous column-major layout, alpha=beta=1, transb=No.
73  // casadi_mtimes_dense takes (x, nrow_x, ncol_x, y, ncol_y, z, tr) and
74  // computes z += op(x) * y where:
75  // tr=0: x is (nrow_x, ncol_x), y is (ncol_x, ncol_y) -> z (nrow_x, ncol_y)
76  // tr=1: x is (nrow_x, ncol_x), y is (nrow_x, ncol_y) -> z (ncol_x, ncol_y)
77  const casadi_int lda_canonical = transa_yes ? k : m;
78  const bool canonical = !transb_yes
79  && alpha == 1.0 && beta == 1.0
80  && lda == lda_canonical && ldb == k && ldc == m;
81 
82  if (canonical) {
83  if (transa_yes) {
84  // z (m, n) += A^T (m, k from k-by-m) * B (k, n)
85  casadi_mtimes_dense<double>(A, k, m, B, n, C, 1);
86  } else {
87  // z (m, n) += A (m, k) * B (k, n)
88  casadi_mtimes_dense<double>(A, m, k, B, n, C, 0);
89  }
90  return;
91  }
92 
93  // General fallback: scale C, then accumulate.
94  if (beta == 0.0) {
95  for (casadi_int j = 0; j < n; ++j) {
96  double* col = C + j * ldc;
97  for (casadi_int i = 0; i < m; ++i) col[i] = 0.0;
98  }
99  } else if (beta != 1.0) {
100  for (casadi_int j = 0; j < n; ++j) {
101  double* col = C + j * ldc;
102  for (casadi_int i = 0; i < m; ++i) col[i] *= beta;
103  }
104  }
105 
106  for (casadi_int j = 0; j < n; ++j) {
107  for (casadi_int l = 0; l < k; ++l) {
108  const double b_lj = transb_yes ? B[j + l * ldb] : B[l + j * ldb];
109  if (b_lj == 0.0) continue;
110  const double scl = alpha * b_lj;
111  double* col = C + j * ldc;
112  if (transa_yes) {
113  const double* a_col = A + l; // walks A^T's column l = A's row l
114  // a_il = A[l + i * lda]
115  for (casadi_int i = 0; i < m; ++i) col[i] += scl * a_col[i * lda];
116  } else {
117  const double* a_col = A + l * lda; // A's column l
118  for (casadi_int i = 0; i < m; ++i) col[i] += scl * a_col[i];
119  }
120  }
121  }
122 }
123 
124 // Shorthand 0 is reserved for the built-in reference impl. It has no Plugin
125 // entry; dispatch_[0] is a permanent nullptr sentinel that's never read
126 // because every dispatch path special-cases sh==0.
127 static const char* REFERENCE_DOC =
128  "Built-in dense BLAS implementation, no external dependency. "
129  "Used by default and as the fallback when other plugins are unavailable.";
130 
131 #ifdef CASADI_CORE_BLAS_DEPENDENCY
132 extern "C" void casadi_load_blas_classic();
133 #ifdef CASADI_L1_BLAS
134 extern "C" void casadi_blas_classic_set_l1_hooks();
135 namespace {
136  const bool _casadi_core_l1 = (casadi_blas_classic_set_l1_hooks(), true);
137 }
138 #endif // CASADI_L1_BLAS
139 #endif // CASADI_CORE_BLAS_DEPENDENCY
140 
141 casadi_int Blas::shorthand_for(const std::string& name) {
142 #ifdef CASADI_CORE_BLAS_DEPENDENCY
143  static bool core_blas_inited = false;
144  if (!core_blas_inited) {
145  core_blas_inited = true; // set first: setDefault re-enters here
146  casadi_load_blas_classic(); // register the absorbed plugin (no dlopen)
147  setDefault("classic"); // sets Blas::default_ (L3 codegen) + hooks
148  }
149 #endif
150  if (name == "reference") return 0;
151 #ifdef CASADI_WITH_THREADSAFE_SYMBOLICS
152  std::lock_guard<std::mutex> lock(Blas::mutex_solvers_);
153 #endif // CASADI_WITH_THREADSAFE_SYMBOLICS
154 
155  // Lazy-init the dispatch_ vector: plant the slot-0 sentinel.
156  if (dispatch_.empty()) {
157  dispatch_.push_back(nullptr); // index 0 == reference, never indexed
158  }
159 
160  auto it = solvers_.find(name);
161  if (it == solvers_.end()) {
162  // Auto-load (lock already held, so pass needs_lock=false)
163  load_plugin(name, true, false);
164  it = solvers_.find(name);
165  casadi_assert_dev(it != solvers_.end());
166  }
167 
168  // Find existing shorthand by pointer identity (small N, linear scan is fine).
169  // Start at 1 — slot 0 is the reference sentinel.
170  const Plugin* p = &it->second;
171  for (casadi_int sh = 1; sh < static_cast<casadi_int>(dispatch_.size()); ++sh) {
172  if (dispatch_[sh] == p) return sh;
173  }
174 
175  // Newly registered plugin: assign next shorthand
176  dispatch_.push_back(p);
177  return static_cast<casadi_int>(dispatch_.size() - 1);
178 }
179 
180 void Blas::dgemm(casadi_int shorthand,
181  int transa, int transb,
182  casadi_int m, casadi_int n, casadi_int k,
183  double alpha,
184  const double* A, casadi_int lda,
185  const double* B, casadi_int ldb,
186  double beta,
187  double* C, casadi_int ldc) {
188  if (shorthand == 0) {
189  // Reference fast path: skip indirection entirely.
190  reference_dgemm(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);
191  return;
192  }
193  // External plugin: trust the caller obtained `shorthand` via shorthand_for(),
194  // which guarantees dispatch_[shorthand] is populated. Hot path, no lock.
195  casadi_assert_dev(shorthand < static_cast<casadi_int>(dispatch_.size()));
196  casadi_assert_dev(dispatch_[shorthand] != nullptr);
197  dispatch_[shorthand]->exposed.dgemm(
198  transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);
199 }
200 
201 void Blas::mtimes(casadi_int shorthand,
202  const double* A, casadi_int m, casadi_int k,
203  const double* B, casadi_int n,
204  double* C) {
205  if (shorthand == 0) {
206  // Reference fast path: arguments are already canonical here, so skip
207  // dgemm's canonical-detection branch and call the loop directly.
208  casadi_mtimes_dense<double>(A, m, k, B, n, C, /*tr=*/0);
209  return;
210  }
211  // External plugin: indirect through dispatch_, no lock. The caller
212  // obtained `shorthand` via shorthand_for() so the slot is populated.
213  casadi_assert_dev(shorthand < static_cast<casadi_int>(dispatch_.size()));
214  casadi_assert_dev(dispatch_[shorthand] != nullptr);
215  dispatch_[shorthand]->exposed.dgemm(
217  m, n, k, 1.0, A, m, B, k, 1.0, C, m);
218 }
219 
220 const char* Blas::name_for_shorthand(casadi_int shorthand) {
221  if (shorthand == 0) return "reference";
222  // Read-only fast path: dispatch_ entries are stable for process lifetime
223  // and shorthand_for() is the sole writer. No lock needed; trust caller.
224  casadi_assert_dev(shorthand < static_cast<casadi_int>(dispatch_.size()));
225  casadi_assert_dev(dispatch_[shorthand] != nullptr);
226  return dispatch_[shorthand]->name;
227 }
228 
229 void Blas::codegen_mtimes(CodeGenerator& g, casadi_int shorthand,
230  const std::string& A,
231  casadi_int m, casadi_int k,
232  const std::string& B, casadi_int n,
233  const std::string& C) {
234  if (shorthand == 0) {
235  // Reference fast path: emit the built-in casadi_mtimes_dense call.
236  g << g.mtimes(A, m, k, B, n, C, false) << '\n';
237  return;
238  }
239  // External plugin: trust the caller obtained `shorthand` via shorthand_for(),
240  // so dispatch_[shorthand] is populated and its codegen_mtimes is non-null
241  // (otherwise the plugin would have failed to register).
242  casadi_assert_dev(shorthand < static_cast<casadi_int>(dispatch_.size()));
243  casadi_assert_dev(dispatch_[shorthand] != nullptr);
244  const Plugin* p = dispatch_[shorthand];
245  casadi_assert(p->exposed.codegen_mtimes != nullptr,
246  "BLAS plugin '" + std::string(p->name) + "' does not implement codegen.");
247  p->exposed.codegen_mtimes(g, A, m, k, B, n, C);
248 }
249 
250 bool has_blas(const std::string& name) {
251  if (name == "reference") return true;
252  return Blas::has_plugin(name);
253 }
254 
255 void load_blas(const std::string& name) {
256  if (name == "reference") return;
257  Blas::load_plugin(name);
258 }
259 
260 std::string doc_blas(const std::string& name) {
261  if (name == "reference") return REFERENCE_DOC;
262  return Blas::getPlugin(name).doc;
263 }
264 
265 void Blas::setDefault(const std::string& name) {
266  default_ = shorthand_for(name);
267 #ifdef CASADI_L1_BLAS
268  // Mirror into the low-level hooks: plugin L1 fn if any, else null (reference).
269  const Exposed* e = default_ ? &dispatch_[default_]->exposed : nullptr;
270  casadi_daxpy_hook = e ? e->daxpy : nullptr;
271  casadi_ddot_hook = e ? e->ddot : nullptr;
272  casadi_dscal_hook = e ? e->dscal : nullptr;
273  casadi_dnrm2_hook = e ? e->dnrm2 : nullptr;
274  casadi_dasum_hook = e ? e->dasum : nullptr;
275  casadi_dcopy_hook = e ? e->dcopy : nullptr;
276 #endif // CASADI_L1_BLAS
277 }
278 
279 std::string Blas::getDefault() {
281 }
282 
284  const std::vector<std::string>& inst) {
285  g.add_include("string.h");
287  "// SYMBOL \"copy\"\n"
288  "void casadi_copy(const casadi_real* x, casadi_int n, casadi_real* y) {\n"
289  " if (!y) return;\n"
290  " if (x) memcpy(y, x, n*sizeof(casadi_real));\n"
291  " else memset(y, 0, n*sizeof(casadi_real));\n"
292  "}\n",
293  inst);
294 }
295 
297  const std::vector<std::string>& inst) {
298  if (!default_) return false;
299  CodegenL1Aux fn = dispatch_[default_]->exposed.codegen_axpy_aux;
300  if (!fn) return false;
301  fn(g, inst);
302  return true;
303 }
304 
306  const std::vector<std::string>& inst) {
307  if (!default_) return false;
308  CodegenL1Aux fn = dispatch_[default_]->exposed.codegen_dot_aux;
309  if (!fn) return false;
310  fn(g, inst);
311  return true;
312 }
313 
315  const std::vector<std::string>& inst) {
316  if (!default_) return false;
317  CodegenL1Aux fn = dispatch_[default_]->exposed.codegen_scal_aux;
318  if (!fn) return false;
319  fn(g, inst);
320  return true;
321 }
322 
324  const std::vector<std::string>& inst) {
325  if (!default_) return false;
326  CodegenL1Aux fn = dispatch_[default_]->exposed.codegen_nrm2_aux;
327  if (!fn) return false;
328  fn(g, inst);
329  return true;
330 }
331 
333  const std::vector<std::string>& inst) {
334  if (!default_) return false;
335  CodegenL1Aux fn = dispatch_[default_]->exposed.codegen_asum_aux;
336  if (!fn) return false;
337  fn(g, inst);
338  return true;
339 }
340 
341 } // namespace casadi
static bool codegen_dot_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:305
static bool codegen_norm_1_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:332
static const std::string infix_
Definition: blas_impl.hpp:139
static casadi_int default_
Definition: blas_impl.hpp:116
static std::string getDefault()
Definition: blas.cpp:279
void(* CodegenL1Aux)(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas_impl.hpp:84
static void codegen_copy_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:283
static void mtimes(casadi_int shorthand, const double *A, casadi_int m, casadi_int k, const double *B, casadi_int n, double *C)
Definition: blas.cpp:201
static bool codegen_scal_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:314
static std::map< std::string, Plugin > solvers_
Definition: blas_impl.hpp:110
static bool codegen_axpy_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:296
static bool codegen_norm_2_aux(CodeGenerator &g, const std::vector< std::string > &inst)
Definition: blas.cpp:323
static std::vector< const Plugin * > dispatch_
Definition: blas_impl.hpp:113
static const char * name_for_shorthand(casadi_int shorthand)
Definition: blas.cpp:220
static void reference_dgemm(int transa, int transb, casadi_int m, casadi_int n, casadi_int k, double alpha, const double *A, casadi_int lda, const double *B, casadi_int ldb, double beta, double *C, casadi_int ldc)
Definition: blas.cpp:62
static casadi_int shorthand_for(const std::string &name)
Definition: blas.cpp:141
static void codegen_mtimes(CodeGenerator &g, casadi_int shorthand, const std::string &A, casadi_int m, casadi_int k, const std::string &B, casadi_int n, const std::string &C)
Definition: blas.cpp:229
static void dgemm(casadi_int shorthand, int transa, int transb, casadi_int m, casadi_int n, casadi_int k, double alpha, const double *A, casadi_int lda, const double *B, casadi_int ldb, double beta, double *C, casadi_int ldc)
Definition: blas.cpp:180
Helper class for C code generation.
std::string mtimes(const std::string &x, const Sparsity &sp_x, const std::string &y, const Sparsity &sp_y, const std::string &z, const Sparsity &sp_z, const std::string &w, bool tr)
Codegen sparse matrix-matrix multiplication.
std::string sanitize_source(const std::string &src, const std::vector< std::string > &inst, bool add_shorthand=true)
Sanitize source files for codegen.
void add_include(const std::string &new_include, bool relative_path=false, const std::string &use_ifdef=std::string())
Add an include file optionally using a relative path "..." instead of an absolute path <....
std::stringstream auxiliaries
static bool has_plugin(const std::string &pname, bool verbose=false)
Check if a plugin is available or can be loaded.
static Plugin & getPlugin(const std::string &pname)
Load and get the creator function.
static Plugin load_plugin(const std::string &pname, bool register_plugin=true, bool needs_lock=true)
Load a plugin dynamically.
The casadi namespace.
Definition: archiver.cpp:28
@ CASADI_BLAS_TRANS
Definition: blas_impl.hpp:42
@ CASADI_BLAS_NO_TRANS
Definition: blas_impl.hpp:41
void CASADI_BLAS_CLASSIC_EXPORT casadi_load_blas_classic()
bool has_blas(const std::string &name)
Definition: blas.cpp:250
std::string doc_blas(const std::string &name)
Definition: blas.cpp:260
void load_blas(const std::string &name)
Definition: blas.cpp:255
static const char * REFERENCE_DOC
Definition: blas.cpp:127