You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@georgemalerbo is interested in automatically generating BLAS wrappers for us. Thanks George!
Before we do that, @mhoemmen could you please provide feedback on the following dscal wrapper?
// Compiled with g++-16 -std=c++23 dscal_example.cpp -lblas
#include<cassert>
#include<vector>
#include<mdspan>
#include<iostream>usingnamespacestd;// Should come from CMake or a config headerusingblas_index_t = int; // BLAS typically uses int for sizes and indicesextern"C" {
// We need to account for the Fortran name mangling, which often appends an underscore to the function name// The mangling should be automatically detectedvoiddscal_(blas_index_t* n, double* a, double* x, blas_index_t* incx);
}
// Which accessors does it make sense to support?template<classSizeType,
::std::size_t ext0,
classLayout,
classAccessor>
// scal requires strided data, so we can enforce this at compile time// What is the difference between is_always_strided and is_strided? Should we support both?requires ( mdspan<double, extents<SizeType, ext0>, Layout, Accessor>::is_always_strided() )
void scale(double a, mdspan<double, extents<SizeType, ext0>, Layout, Accessor> x) {
blas_index_t n = x.extent(0);
// This should be checked before calling the function// Like, in a "does it make sense to call BLAS?" functionassert(n <= std::numeric_limits<blas_index_t>::max() && "Size of x exceeds maximum value for blas_index_t");
// Assuming contiguous data, this will be 1 for a standard vector// This code will still work for non-contiguous data, but the performance may be worse due to cache missesblas_index_t incx = x.stride(0);
dscal_(&n, &a, x.data_handle(), &incx);
}
intmain() {
vector<double> x_data = {1.0, 2.0, 3.0};
auto x = mdspan(x_data.data(), x_data.size());
double a = 2.0;
scale(a, x);
for(int i = 0; i < x.extent(0); ++i) {
cout << x[i] << "";
}
cout << endl;
return0;
}
Putting this declaration inside main would avoid exposing it to the rest of the program.
// We need to account for the Fortran name mangling, which often appends an underscore to the function name
// The mangling should be automatically detected
I might suggest just starting with a C BLAS. OpenBLAS should have a C BLAS with which you can start. If you don't have a C BLAS, then the right encapsulation would be to provide a C BLAS wrapper around a Fortran BLAS. That's a separate problem that you can solve separately, later : - ) . This would also solve the "what is the index type?" problem: You'll have a C BLAS header, so you can examine the function types to make sure that the index type is int.
template<class SizeType,
We haven't gone through the reference std::linalg implementation yet to rename SizeType to IndexType, but we can fix that here : - ) .
// This should be checked before calling the function
// Like, in a "does it make sense to call BLAS?" function
Would you consider the following pattern?
template<class Scalar, class ElementType, class IndexType, std::size_t Ext0, class Layout, class Accessor>
void scale(Scalar a, mdspan<Scalar, extents<IndexType, Ext0>, Layout, Accessor> x) {
bool done = false;
if constexpr (maybe_can_blas_scale<Scalar, decltype(x)>()) { // compile-time check
// Run-time check.
// If it can call the BLAS, then call the BLAS and return true.
// Otherwise, return false.
done = try_blas_scale(a, x);
}
if (! done) {
generic_scale(a, x);
}
}
Christian Trott and I developed this pattern for kokkos-kernels. The idea is that the user-facing "outer" function is totally generic and works for all types that std::linalg supports. (That's why there are no extra "requires" clauses or other constraints on it.) The implementation of the "outer" function then uses first compile-time information, then run-time information to decide if it can call the BLAS. We fuse the run-time check with actually calling the BLAS (if the check passes) into one function, try_blas_scale. Putting the run-time check inside the if constexpr ensures that we only compile it if the types make sense.
generic_scale is just generic straightforward code. It's the "fall-back" implementation in case we can't call the BLAS.
template<class Scalar, class ElementType, class IndexType, std::size_t Ext0, class Layout, class Accessor>
void generic_scale(Scalar a, mdspan<Scalar, extents<IndexType, Ext0>, Layout, Accessor> x) {
std::ranges::for_each(std::views::indices(x.extent(0)), [=] (IndexType k) { x[k] *= a; });
// Or, if you don't want to use ranges, you can do this.
// IndexType e0 = x.extent(0);
// for (IndexType k = 0; k < e0; ++k) {
// x[k] *= a;
// }
}
maybe_can_blas_scale only tests the template parameters of scale -- just the types. We combine all the mdspan-related template parameters of scale into a single MdspanType template parameter, just so that calling maybe_can_blas_scale looks as much like calling scale as possible (e.g., two template parameters vs. two function parameters). maybe_can_blas_scale takes template parameters instead of function parameters because that makes sure that everything is known at compile time. Another option would be to make this a trait (exposed as a variable template). Adding constexpr to the function declaration tells the compiler that this function can be called in a "constant expression" (at compile time).
template<class ValueType>
constexpr bool is_blas_value_type_v =
std::is_same_v<ValueType, float> ||
std::is_same_v<ValueType, double> ||
std::is_same_v<ValueType, std::complex<float>> ||
std::is_same_v<ValueType, std::complex<double>>;
template<class Layout>
constexpr bool is_blas_layout_type_v =
// Assume that we have a C BLAS, which accepts
// both row-major and column-major layouts.
//
// This just means that the layouts COULD be valid.
// For layout_stride, we need to check the strides first.
std::is_same_v<Layout, std::layout_left> ||
std::is_same_v<Layout, std::layout_left_padded> ||
std::is_same_v<Layout, std::layout_right> ||
std::is_same_v<Layout, std::layout_right_padded> ||
std::is_same_v<Layout, std::layout_stride>;
}
// The BLAS accepts accessors that deal with pointers to memory:
// default_accessor and aligned_accessor.
// Those are both templated classes, so we can't just use is_same_v directly.
//
// scale doesn't accept conjugated_accessor or scaled_accessor
// because those are read-only accessors, and scale needs to
// write to the mdspan's elements.
template<class Accessor>
constexpr bool is_default_accessor_v = false;
template<class ElementType>
constexpr bool is_default_accessor_v<std::default_accessor<ElementType>> = true;
template<class Accessor>
constexpr bool is_aligned_accessor_v = false;
template<class ElementType, std::size_t ByteAlignment>
constexpr bool is_aligned_accessor_v<std::aligned_accessor<ElementType, ByteAlignment>> = true;
template<class Accessor>
constexpr bool is_blas_accessor_type_v =
is_default_accessor_v<Accessor> ||
is_aligned_accessor_v<Accessor>;
// We made the above queries traits, with their typical `_v` prefix.
// We make maybe_can_blas_scale() a function.
// That's a matter of taste; it could be a trait too.
template<class Scalar, class MdspanType>
constexpr void maybe_can_blas_scale() {
// It's OK if Scalar and value_type aren't the same,
// as long as we can convert Scalar to value_type.
using value_type = typename MdspanType::value_type;
constexpr bool blas_value_type =
std::is_convertible_v<Scalar, value_type> &&
is_blas_value_type_v<value_type>();
constexpr bool blas_layout =
is_blas_layout_type_v<typename MdspanType::layout_type>;
constexpr bool blas_accessor =
is_blas_accessor_v<typename MdspanType::accessor_type>;
return blas_value_type && blas_layout && blas_accessor;
}
@amklinv-nnl Btw, the implementation uses a slightly different customization mechanism. It permits customization on the execution policy. I haven't looked at this for a while, but the important thing is not to get in the way of that. The technique I showed would happen in the existing serial "fall-back" implementation.
Thanks for your help! I'm really interested in contributing in any way I can. As soon as I started to really look closely at how those types and arguments are structured across the parts of the cblas.h header I needed, I realized a lot of it could be broken down into a giant repeating grid. I recently created a python script as an experiment.
I haven't uploaded anything to any repository yet but this is what I've been researching and working on recently. Let me know what you think.
I wanted to create a short and concise script as clean and readable as possible, and most of the repeated parameters from the cblas.h signatures map nicely to tiny two letter shorthand variables at the top of my file with definition builders for the routine arguments
With those in place a lot of it collapses into a clean and readable list where the shorthand blocks plug in like this:
Routine("gemm", ALL, (LO, TRA, TRB, M, N, K, AL, Ac, LDA, Bc, LDB, BE, Cm, LDC), level=3)
For the edge cases it was a matter of patching the script whenever it hit an exception to the conventions of the others. To account for one of those cases, the type list includes a real_prefix tracker, and the naming rule handles it directly:
cblas="cblas_{real_prefix}{prefix}nrm2"
Complex dot product functions, those ended up being one of the odd cases because I see they don’t actually return a value directly and instead they pass the result out through an extra pointer parameter at the very end of the function call. The generator handles that by passing along a small layout override rule, out_param, so those routines can be emitted differently from the normal return value cases.
There's a small conditional formatting rule keeps everything aligned at 80 columns and under with a helper to make sure argument lists slice up and line wrap into clean balanced blocks of code.
By designing the script and mapping it out in this way, it comes out to about 280 lines of python code, which outputs about 2,600 lines of C++ declarations and definitions.
Thanks for putting this up I think I see it clearly now
I'd been thinking of the cblas backend as it's own self contained layer that I figured would just drop into stdBLAS once finished: a codegen layer emitting the cblas_* forwarders for all four scalar types from one catalog, and a dispatch layer on top.
flowchart LR
G[generator] --> T["cblas_* forwarders<br/>s · d · c · z"]
T --> D["maybe_can_blas_* / try_blas_*<br/>order · lda · aliasing · tags"]
D -.->|drop in later| SB["stdBLAS<br/>serial fallback"]
Loading
But I get it now. The integration step is the point, so there's no need for a separate layer. The wrappers go straight into the real files, around the existing bodies.
flowchart LR
G[generator] --> M["maybe_can_blas_scale<br/>try_blas_scale"]
H["blas_helpers.hpp<br/>traits"] --> M
M --> S["scale(inline_exec_t&&)<br/>serial fallback"]
S --> P["blas1_scale.hpp"]
Loading
Is that what we're after? If so I can start reworking my generator script and experiment with this :)
Hi @georgemalerbo ! You're making great progress! I see that you're understanding the patterns in the BLAS' abbreviated function names more and more.
The idea is that you can start with an existing C BLAS, with its cblas.h header file. You don't have to generate the C BLAS functions. They already exist for you. Just start by installing the OpenBLAS and including its cblas.h header in your library.
We're focusing on this step because OpenBLAS is an open-source library with good performance on a variety of hardware. Different BLAS libraries that provide a C BLAS might have different header file names, but we can deal with that in the build system. Some BLAS libraries only have a Fortran BLAS, but a good way to deal with calling Fortran BLAS functions from C or C++ is to build a C BLAS. At that point, your script for generating C BLAS function declarations would be helpful, but we don't have to worry about that yet.
Does this make sense? A good way to start would be with my suggested DSCAL wrapper above.
I built #331 locally and tried it against a few different cblas libraries.
if (x.is_strided() && n <= std::numeric_limits<CBLAS_INDEX>::max()) {
works on OpenBLAS and MKL, but it doesn't compile against BLIS or AMD's AOCL they don't define CBLAS_INDEX:
error: 'CBLAS_INDEX' was not declared in this scope; did you mean 'CBLAS_SIDE'?
18 | n <= std::numeric_limits<CBLAS_INDEX>::max()
| ^~~~~~~~~~~
| CBLAS_SIDE
error: template argument 1 is invalid
AMD's header doesn't have it but MKL's does:
$ grep CBLAS_INDEX /usr/local/amd-blis/include/LP64/cblas.h
nothing
$ grep "#define CBLAS_INDEX" /opt/intel/oneapi/mkl/2026.0/include/mkl_cblas.h
#define CBLAS_INDEX size_t /* this may vary between platforms */
@mhoemmen Is the "good enough" solution to temporarily enforce an (optional) dependency on OpenBLAS or MKL as a BLAS provider? There is a mechanism to do this in CMake.
@georgemalerbo Thanks for sharing your discovery! You reminded me of something important, which is that even the C binding of the BLAS does not necessarily promise what integer type or size that it uses.
I was looking at how kokkos kernels handles this....
It looks like kokkos-kernels knows that MKL defines MKL_INT as the C BLAS index type, and guesses that other libraries just use int.
It's reasonable to assume that a C BLAS will either use int or something bigger. (Please see Note below.)
Given that we're using a C BLAS and therefore have a header with declarations, we don't need to generate extern "C" declarations. Therefore, we don't need to know what index type the C BLAS uses, as long as we know its minimum range for all C BLAS implementations, which is 0, 1, 2, ..., std::numeric_limits<int>::max().
I conclude from this that we don't need linalg_int or LINALG_INT. We only need to check that the dimensions are within bounds of int. That will cover every BLAS we are likely to encounter, including any reasonable Fortran BLAS.
Note: Different compilers and systems have different ideas about the sizes of int, long, and pointer types such as void*. We write "LP64" for the case where int is 32 bits, long is 64 bits, and pointers (P) are 64 bits. We write "ILP64" for the case where int, long, and pointers are all 64 bits. Some BLAS implementations support both, by some combination of linking in a different library and/or using a different header (for the C BLAS; Fortran BLAS implementations might not have a header). The C and C++ Standards require that int have at least 16 bits, long have at least 32 bits, and long long have at least 64 bits. The BLAS library might disagree with your compiler settings, so you can't just ask your compiler for sizeof(int). There are many possibilities, and trying to deduce them is hard. It's perhaps best just to document your requirements.
Is the "good enough" solution to temporarily enforce an (optional) dependency on OpenBLAS or MKL as a BLAS provider? There is a mechanism to do this in CMake.
As long as we have a C BLAS header and assume that the C BLAS uses an index type no smaller than int, then we don't have to worry what index type the C BLAS uses. This is because we only care in order to check bounds.
It's reasonable to assume that a C BLAS will either use int or something bigger.
That will cover every BLAS we are likely to encounter, including any reasonable Fortran BLAS.
OCD laughs in the face of reason and goes brrrrrrr with all the possibilities of breaking amklinv-nnl#1 (and also KokkosKernels maybe?)...
Scenario 1: compiler defines int as 16 bits
Interface incorrectly assumes vectors of length 40k cannot be passed to CBLAS. Potentially inefficient, but not incorrect.
Scenario 2: compiler defines int as 64 bits, but CBLAS library is LP64
Interface incorrectly assumes vectors of length 3B can be passed to CBLAS. This would be bad, but also...just how much RAM does this hypothetical user have?
Do we consider these scenarios unreasonable and ignore them/document "don't do that", or do we protect this hypothetical user from themselves? "Yes we ignore them" is an acceptable answer; just wanted to check 🐕
Having a C BLAS header simplifies solving many of these problems. You just have to make sure that the C BLAS header matches the C BLAS library. It's the user's responsibility to follow their C BLAS' configuration instructions in order to make the C BLAS library match the C BLAS header.
In practice, I've only seen this with LP64 vs. ILP64. Getting the header to match the library might involve defining a macro and linking to the correct library. We can let this be the user's responsibility.
template<classR, classA, class... Rest> A first_param(R(*)(A, Rest...));
using cblas_index = decltype(first_param(cblas_dscal));
@georgemalerbo That's a clever trick! This is one of the advantages of having a C BLAS header file, that it has function declarations. That's why I suggested starting with a C BLAS. I talk a little bit about this in P1674.
If you like, you could define cblas_index in a private inner namespace and use that in your size tests instead of int. The current approach is perfectly fine for now, though.
Thanks for the private namespace tip. Previous tests were on x86_64 with an Intel cpu. It took a bit of persistence but I was able to get access to a cloud GH200 for a short time over the weekend and ran the same test with g++ and nvc++ to see what it would do on Grace + NVPL cblas. Looks like it still holds 4 bytes LP64, 8 bytes ILP64
uname : Linux 6.8.0-1046-nvidia-64k aarch64
CPU : Neoverse-V2 (64 cores, 525Gi RAM)
Activity
mhoemmen commentedon May 14, 2026
Thanks @georgemalerbo for helping out! : - )
Putting this declaration inside
mainwould avoid exposing it to the rest of the program.I might suggest just starting with a C BLAS. OpenBLAS should have a C BLAS with which you can start. If you don't have a C BLAS, then the right encapsulation would be to provide a C BLAS wrapper around a Fortran BLAS. That's a separate problem that you can solve separately, later : - ) . This would also solve the "what is the index type?" problem: You'll have a C BLAS header, so you can examine the function types to make sure that the index type is
int.We haven't gone through the reference std::linalg implementation yet to rename
SizeTypetoIndexType, but we can fix that here : - ) .Would you consider the following pattern?
Christian Trott and I developed this pattern for kokkos-kernels. The idea is that the user-facing "outer" function is totally generic and works for all types that std::linalg supports. (That's why there are no extra "requires" clauses or other constraints on it.) The implementation of the "outer" function then uses first compile-time information, then run-time information to decide if it can call the BLAS. We fuse the run-time check with actually calling the BLAS (if the check passes) into one function,
try_blas_scale. Putting the run-time check inside theif constexprensures that we only compile it if the types make sense.mhoemmen commentedon May 14, 2026
generic_scaleis just generic straightforward code. It's the "fall-back" implementation in case we can't call the BLAS.mhoemmen commentedon May 14, 2026
maybe_can_blas_scaleonly tests the template parameters ofscale-- just the types. We combine all themdspan-related template parameters ofscaleinto a singleMdspanTypetemplate parameter, just so that callingmaybe_can_blas_scalelooks as much like callingscaleas possible (e.g., two template parameters vs. two function parameters).maybe_can_blas_scaletakes template parameters instead of function parameters because that makes sure that everything is known at compile time. Another option would be to make this a trait (exposed as a variable template). Addingconstexprto the function declaration tells the compiler that this function can be called in a "constant expression" (at compile time).amklinv-nnl commentedon May 14, 2026
My OCD is sufficiently satisfied with that response.
I was just about to do that anyway!
Yup! It almost sounds like y'all already put a lot of thought into it!
What is the oldest standard version the reference implementation has to support? Just want to know if I need to guard ranges with an ifdef.
Thanks so much for the detailed explanation!
mhoemmen commentedon May 14, 2026
@amklinv-nnl wrote:
Ah, sorry, I forgot that we should still support C++17. For loops it is, then.
Thanks for working on this! : - D
mhoemmen commentedon May 14, 2026
@amklinv-nnl Btw, the implementation uses a slightly different customization mechanism. It permits customization on the execution policy. I haven't looked at this for a while, but the important thing is not to get in the way of that. The technique I showed would happen in the existing serial "fall-back" implementation.
georgemalerbo commentedon May 20, 2026
Thanks for your help! I'm really interested in contributing in any way I can. As soon as I started to really look closely at how those types and arguments are structured across the parts of the cblas.h header I needed, I realized a lot of it could be broken down into a giant repeating grid. I recently created a python script as an experiment.
I haven't uploaded anything to any repository yet but this is what I've been researching and working on recently. Let me know what you think.
I wanted to create a short and concise script as clean and readable as possible, and most of the repeated parameters from the cblas.h signatures map nicely to tiny two letter shorthand variables at the top of my file with definition builders for the routine arguments
With those in place a lot of it collapses into a clean and readable list where the shorthand blocks plug in like this:
For the edge cases it was a matter of patching the script whenever it hit an exception to the conventions of the others. To account for one of those cases, the type list includes a real_prefix tracker, and the naming rule handles it directly:
Complex dot product functions, those ended up being one of the odd cases because I see they don’t actually return a value directly and instead they pass the result out through an extra pointer parameter at the very end of the function call. The generator handles that by passing along a small layout override rule, out_param, so those routines can be emitted differently from the normal return value cases.
There's a small conditional formatting rule keeps everything aligned at 80 columns and under with a helper to make sure argument lists slice up and line wrap into clean balanced blocks of code.
By designing the script and mapping it out in this way, it comes out to about 280 lines of python code, which outputs about 2,600 lines of C++ declarations and definitions.
georgemalerbo commentedon May 21, 2026
Thanks for putting this up I think I see it clearly now
I'd been thinking of the cblas backend as it's own self contained layer that I figured would just drop into stdBLAS once finished: a codegen layer emitting the cblas_* forwarders for all four scalar types from one catalog, and a dispatch layer on top.
flowchart LR G[generator] --> T["cblas_* forwarders<br/>s · d · c · z"] T --> D["maybe_can_blas_* / try_blas_*<br/>order · lda · aliasing · tags"] D -.->|drop in later| SB["stdBLAS<br/>serial fallback"]But I get it now. The integration step is the point, so there's no need for a separate layer. The wrappers go straight into the real files, around the existing bodies.
flowchart LR G[generator] --> M["maybe_can_blas_scale<br/>try_blas_scale"] H["blas_helpers.hpp<br/>traits"] --> M M --> S["scale(inline_exec_t&&)<br/>serial fallback"] S --> P["blas1_scale.hpp"]Is that what we're after? If so I can start reworking my generator script and experiment with this :)
mhoemmen commentedon May 22, 2026
Hi @georgemalerbo ! You're making great progress! I see that you're understanding the patterns in the BLAS' abbreviated function names more and more.
The idea is that you can start with an existing C BLAS, with its cblas.h header file. You don't have to generate the C BLAS functions. They already exist for you. Just start by installing the OpenBLAS and including its cblas.h header in your library.
We're focusing on this step because OpenBLAS is an open-source library with good performance on a variety of hardware. Different BLAS libraries that provide a C BLAS might have different header file names, but we can deal with that in the build system. Some BLAS libraries only have a Fortran BLAS, but a good way to deal with calling Fortran BLAS functions from C or C++ is to build a C BLAS. At that point, your script for generating C BLAS function declarations would be helpful, but we don't have to worry about that yet.
Does this make sense? A good way to start would be with my suggested DSCAL wrapper above.
amklinv-nnl commentedon May 26, 2026
Hi @mhoemmen! I tried implementing your suggested scal wrapper in PR #331. Could you review and make suggestions at your convenience?
georgemalerbo commentedon May 26, 2026
just wanted to share something I discovered
I built #331 locally and tried it against a few different cblas libraries.
works on OpenBLAS and MKL, but it doesn't compile against BLIS or AMD's AOCL they don't define
CBLAS_INDEX:AMD's header doesn't have it but MKL's does:
Just figured I'd share hope it helps
amklinv-nnl commentedon May 27, 2026
@mhoemmen Is the "good enough" solution to temporarily enforce an (optional) dependency on OpenBLAS or MKL as a BLAS provider? There is a mechanism to do this in CMake.
georgemalerbo commentedon May 27, 2026
I was looking at how kokkos kernels handles this, They define their own integer type,
KK_INT:We could mirror that and we call ours
linalg_intor something and define it in blas_helpers.hpp:and then that line becomes:
if (x.is_strided() && n <= std::numeric_limits<linalg_int>::max()) {I tried this locally against OpenBLAS, MKL, BLIS, and AMD and it compiles on all four.
mhoemmen commentedon May 27, 2026
@georgemalerbo Thanks for sharing your discovery! You reminded me of something important, which is that even the C binding of the BLAS does not necessarily promise what integer type or size that it uses.
It looks like kokkos-kernels knows that MKL defines
MKL_INTas the C BLAS index type, and guesses that other libraries just useint.It's reasonable to assume that a C BLAS will either use
intor something bigger. (Please see Note below.)Given that we're using a C BLAS and therefore have a header with declarations, we don't need to generate
extern "C"declarations. Therefore, we don't need to know what index type the C BLAS uses, as long as we know its minimum range for all C BLAS implementations, which is 0, 1, 2, ...,std::numeric_limits<int>::max().I conclude from this that we don't need
linalg_intorLINALG_INT. We only need to check that the dimensions are within bounds ofint. That will cover every BLAS we are likely to encounter, including any reasonable Fortran BLAS.Note: Different compilers and systems have different ideas about the sizes of
int,long, and pointer types such asvoid*. We write "LP64" for the case whereintis 32 bits,longis 64 bits, and pointers (P) are 64 bits. We write "ILP64" for the case whereint,long, and pointers are all 64 bits. Some BLAS implementations support both, by some combination of linking in a different library and/or using a different header (for the C BLAS; Fortran BLAS implementations might not have a header). The C and C++ Standards require thatinthave at least 16 bits,longhave at least 32 bits, andlong longhave at least 64 bits. The BLAS library might disagree with your compiler settings, so you can't just ask your compiler forsizeof(int). There are many possibilities, and trying to deduce them is hard. It's perhaps best just to document your requirements.@amklinv-nnl wrote:
As long as we have a C BLAS header and assume that the C BLAS uses an index type no smaller than
int, then we don't have to worry what index type the C BLAS uses. This is because we only care in order to check bounds.georgemalerbo commentedon May 27, 2026
Thanks @mhoemmen. the PR amklinv-nnl#1 targeting @amklinv-nnl's
wrap_scalhas been updated to useintamklinv-nnl commentedon May 28, 2026
OCD laughs in the face of reason and goes brrrrrrr with all the possibilities of breaking amklinv-nnl#1 (and also KokkosKernels maybe?)...
Scenario 1: compiler defines
intas 16 bitsInterface incorrectly assumes vectors of length 40k cannot be passed to CBLAS. Potentially inefficient, but not incorrect.
Scenario 2: compiler defines
intas 64 bits, but CBLAS library is LP64Interface incorrectly assumes vectors of length 3B can be passed to CBLAS. This would be bad, but also...just how much RAM does this hypothetical user have?
Do we consider these scenarios unreasonable and ignore them/document "don't do that", or do we protect this hypothetical user from themselves? "Yes we ignore them" is an acceptable answer; just wanted to check 🐕
mhoemmen commentedon May 28, 2026
Having a C BLAS header simplifies solving many of these problems. You just have to make sure that the C BLAS header matches the C BLAS library. It's the user's responsibility to follow their C BLAS' configuration instructions in order to make the C BLAS library match the C BLAS header.
In practice, I've only seen this with LP64 vs. ILP64. Getting the header to match the library might involve defining a macro and linking to the correct library. We can let this be the user's responsibility.
@amklinv-nnl wrote:
If you have a C BLAS header, then it's included in your code and your compiler sees it.
sizeof(int)is the same.You're on a platform where
sizeof(int) == 2. Vectors of length 40k are probably quite long there.georgemalerbo commentedon May 29, 2026
Out of curiosity I read the index width off a real cblas_dscal declaration OpenBLAS, MKL, and AMD, across LP64 and ILP64 under g++ and icpx:
and the same six under icpx:
intstayed 4 bytes everywhere. The index is 4 on LP64 and 8 on ILP64, soint <= indexholds!mhoemmen commentedon May 31, 2026
@georgemalerbo That's a clever trick! This is one of the advantages of having a C BLAS header file, that it has function declarations. That's why I suggested starting with a C BLAS. I talk a little bit about this in P1674.
If you like, you could define
cblas_indexin a private inner namespace and use that in your size tests instead ofint. The current approach is perfectly fine for now, though.georgemalerbo commentedon Jun 1, 2026
Thanks for the private namespace tip. Previous tests were on x86_64 with an Intel cpu. It took a bit of persistence but I was able to get access to a cloud GH200 for a short time over the weekend and ran the same test with g++ and nvc++ to see what it would do on Grace + NVPL cblas. Looks like it still holds 4 bytes LP64, 8 bytes ILP64
Add a generator for the CBLAS compile test