Skip to content

wrapping a vendor dscal #328

Open
Open

Description

@amklinv-nnl

@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>

using namespace std;

// Should come from CMake or a config header
using blas_index_t = int; // BLAS typically uses int for sizes and indices

extern "C" {
    // We need to account for the Fortran name mangling, which often appends an underscore to the function name
    // The mangling should be automatically detected
    void dscal_(blas_index_t* n, double* a, double* x, blas_index_t* incx);
}

// Which accessors does it make sense to support?
template<class SizeType,
         ::std::size_t ext0,
         class Layout,
         class Accessor>
// 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?" function
    assert(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 misses
    blas_index_t incx = x.stride(0);
    dscal_(&n, &a, x.data_handle(), &incx);
}

int main() {
    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;

    return 0;
}

Activity

  1. mhoemmen commented on May 14, 2026

    @mhoemmen
    Contributor

    Thanks @georgemalerbo for helping out! : - )

    using namespace std;
    

    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.

  2. mhoemmen commented on May 14, 2026

    @mhoemmen
    Contributor

    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; 
      // } 
    }
    
  3. mhoemmen commented on May 14, 2026

    @mhoemmen
    Contributor

    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;
    }  
    
  4. amklinv-nnl commented on May 14, 2026

    @amklinv-nnl
    ContributorAuthor

    provide a C BLAS wrapper around a Fortran BLAS. That's a separate problem that you can solve separately, later

    My OCD is sufficiently satisfied with that response.

    We haven't gone through the reference std::linalg implementation yet to rename SizeType to IndexType, but we can fix that here.

    I was just about to do that anyway!

    Would you consider the following pattern?

    Yup! It almost sounds like y'all already put a lot of thought into it!

    Or, if you don't want to use ranges, you can do this.

    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!

  5. mhoemmen commented on May 14, 2026

    @mhoemmen
    Contributor

    @amklinv-nnl wrote:

    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.

    Ah, sorry, I forgot that we should still support C++17. For loops it is, then.

    Thanks for working on this! : - D

  6. mhoemmen commented on May 14, 2026

    @mhoemmen
    Contributor

    @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.

  7. georgemalerbo commented on May 20, 2026

    @georgemalerbo

    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

    Int        = lambda n: Param(n, "blas_int_t {n}")
    Scalar     = lambda n: Param(n, "{cpp} {n}", arg_fmt_complex="&{n}")
    RealScalar = lambda n: Param(n, "{real} {n}")
    Const      = lambda n: Param(n, "const {cpp}* {n}")
    Mut        = lambda n: Param(n, "{cpp}* {n}")
    MutReal    = lambda n: Param(n, "{real}* {n}")
    
    LO, UP, TR, DI, SD   = Order("LAYOUT"), Uplo("UPLO"), Trans("TRANS"), Diag("DIAG"), Side("SIDE")
    TRA, TRB             = Trans("TRANSA"), Trans("TRANSB")
    M, N, K              = Int("M"), Int("N"), Int("K")
    LDA, LDB, LDC        = Int("LDA"), Int("LDB"), Int("LDC")
    INCX, INCY           = Int("INCX"), Int("INCY")
    Xc, Xm, Yc, Ym       = Const("X"), Mut("X"), Const("Y"), Mut("Y")
    Ac, Am, Bc, Bm, Cm   = Const("A"), Mut("A"), Const("B"), Mut("B"), Mut("C")
    AL, BE, ALr, BEr     = Scalar("ALPHA"), Scalar("BETA"), RealScalar("ALPHA"), RealScalar("BETA")

    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.

  8. georgemalerbo commented on May 21, 2026

    @georgemalerbo

    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 :)

  9. mhoemmen commented on May 22, 2026

    @mhoemmen
    Contributor

    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.

  10. amklinv-nnl commented on May 26, 2026

    @amklinv-nnl
    ContributorAuthor

    Hi @mhoemmen! I tried implementing your suggested scal wrapper in PR #331. Could you review and make suggestions at your convenience?

  11. georgemalerbo commented on May 26, 2026

    @georgemalerbo

    just wanted to share something I discovered

    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 */
    

    Just figured I'd share hope it helps

  12. amklinv-nnl commented on May 27, 2026

    @amklinv-nnl
    ContributorAuthor

    @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.

  13. georgemalerbo commented on May 27, 2026

    @georgemalerbo

    I was looking at how kokkos kernels handles this, They define their own integer type, KK_INT:

    #if defined(KOKKOSKERNELS_ENABLE_TPL_MKL) && defined(MKL_PROVIDES_BLAS_LAPACK)
    using KK_INT = MKL_INT;
    #else
    using KK_INT = int;
    #endif

    We could mirror that and we call ours linalg_int or something and define it in blas_helpers.hpp:

    #ifndef LINALG_INT
    #define LINALG_INT int
    #endif
    using linalg_int = LINALG_INT;

    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.

  14. mhoemmen commented on May 27, 2026

    @mhoemmen
    Contributor

    @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.

    @amklinv-nnl wrote:

    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.

  15. georgemalerbo commented on May 27, 2026

    @georgemalerbo

    Thanks @mhoemmen. the PR amklinv-nnl#1 targeting @amklinv-nnl's wrap_scal has been updated to use int

  16. amklinv-nnl commented on May 28, 2026

    @amklinv-nnl
    ContributorAuthor

    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 🐕

  17. mhoemmen commented on May 28, 2026

    @mhoemmen
    Contributor

    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:

    Scenario 2: compiler defines int as 64 bits, but CBLAS library is LP64

    If you have a C BLAS header, then it's included in your code and your compiler sees it. sizeof(int) is the same.

    Interface incorrectly assumes vectors of length 40k cannot be passed to CBLAS. Potentially inefficient, but not incorrect.

    You're on a platform where sizeof(int) == 2. Vectors of length 40k are probably quite long there.

  18. georgemalerbo commented on May 29, 2026

    @georgemalerbo

    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:

    #include <stdio.h>
    
    template<class R, class A, class... Rest> A first_param(R(*)(A, Rest...));
    using cblas_index = decltype(first_param(cblas_dscal));
    
    int main() {
        const char* result;
        if (sizeof(int) <= sizeof(cblas_index)) {
            result = "true";
        } else {
            result = "false";
        }
        printf("sizeof(int) = %zu, sizeof(cblas_dscal's N) = %zu, int <= index: %s\n",
               sizeof(int), sizeof(cblas_index), result);
    }
    $ g++ -include cblas.h -I/usr/include/x86_64-linux-gnu widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ g++ -include cblas.h -DOPENBLAS_USE64BITINT -I/usr/include/x86_64-linux-gnu widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    $ g++ -include mkl_cblas.h -I/opt/intel/oneapi/mkl/2026.0/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ g++ -include mkl_cblas.h -DMKL_ILP64 -I/opt/intel/oneapi/mkl/2026.0/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    $ g++ -include cblas.h -I/usr/local/amd-blis/include/LP64 widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ g++ -include cblas.h -I/usr/local/amd-blis/include/ILP64 widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    

    and the same six under icpx:

    $ icpx -include cblas.h -I/usr/include/x86_64-linux-gnu widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ icpx -include cblas.h -DOPENBLAS_USE64BITINT -I/usr/include/x86_64-linux-gnu widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    $ icpx -include mkl_cblas.h -I/opt/intel/oneapi/mkl/2026.0/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ icpx -include mkl_cblas.h -DMKL_ILP64 -I/opt/intel/oneapi/mkl/2026.0/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    $ icpx -include cblas.h -I/usr/local/amd-blis/include/LP64 widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ icpx -include cblas.h -I/usr/local/amd-blis/include/ILP64 widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    

    int stayed 4 bytes everywhere. The index is 4 on LP64 and 8 on ILP64, so int <= index holds!

  19. mhoemmen commented on May 31, 2026

    @mhoemmen
    Contributor
    template<class R, class A, 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.

  20. georgemalerbo commented on Jun 1, 2026

    @georgemalerbo

    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)
    
    $ g++ -include nvpl_blas_cblas.h -I.../math_libs/nvpl/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ g++ -DNVPL_ILP64 -include nvpl_blas_cblas.h -I.../math_libs/nvpl/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    
    $ nvc++ -include nvpl_blas_cblas.h -I.../math_libs/nvpl/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 4, int <= index: true
    $ nvc++ -DNVPL_ILP64 -include nvpl_blas_cblas.h -I.../math_libs/nvpl/include widths.cpp -o widths && ./widths
    sizeof(int) = 4, sizeof(cblas_dscal's N) = 8, int <= index: true
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions