Vector
Before allocating memory or launching a kernel, it is worth getting comfortable with Vec.
alpaka uses vectors for extents, indices, and frame shapes, so understanding the basic access rules early avoids many beginner mistakes later.
A vector is designed for integral values, but using a vector with floating-point numbers also works.
The elements of the first four dimensions can be accessed either via an index (e.g., vec[0]) or via a named component (e.g., vec.x()).
The order of the components is w/z/y/x.
This is the reverse order compared to CUDA or HIP, where x/y/z is used.
The mapping of the index position [1] to the named component is as follows:
alpaka:
vec.w() -> vec[0]; vec.z() -> vec[1]; vec.y() -> vec[2]; vec.x() -> vec[3];CUDA/HIP:
vec.x() -> vec[0]; vec.y() -> vec[1]; vec.z() -> vec[2];
Unlike CUDA/HIP, alpaka vectors are not limited to three dimensions but can have any number of dimensions.
If the alpaka vector is smaller than 4D, not all named components are available.
For example, a 3D vector does not have the .w() component.
If, on the other hand, the vector is larger than 4D, all additional dimensions can only be accessed via the access operator.
Vec
// create a one-dimensional vector of unsigned int constexpr auto vec0 = Vec{3u}; // equal to constexpr auto vec1 = Vec<uint32_t, 1u>{3u}; // a vector is not required to be constexpr auto vec2 = Vec{3u};
A vector does not implicitly cast the value type except during initialization.
// explicit type conversion from double to unsigned int constexpr auto vec3 = Vec<uint32_t, 1u>{3.0}; static_assert(std::is_same_v<ALPAKA_TYPEOF(vec0), ALPAKA_TYPEOF(vec3)>);
The dimension of the vector can be queried via dim().
// check the number of components aka dimensions auto vec4 = Vec<uint32_t, 1u>{42u}; static_assert(vec4.dim() == 1u);
The dimensions in a multidimensional vector can be accessed via named functions or indices.
// create a three-dimensional vector of uint32_t constexpr auto vec = Vec{5u, 7u, 11u}; // x is the fast moving index in cases where a vector is used to describe an index within an index space static_assert(vec.x() == 11u); static_assert(vec[2u] == 11u); static_assert(vec.y() == 7u); static_assert(vec[1u] == 7u); static_assert(vec.z() == 5u); static_assert(vec[0u] == 5u); static_assert(vec.dim() == 3u);Warning
If you are familiar with CUDA/HIP, these named components are well-suited for porting CUDA/HIP algorithms to alpaka. However, be careful when using multidimensional data structures from CUDA/HIP applications in alpaka applications, as the order of the indices is reversed for these named components.
The next example shows how to iterate over a C array where the size is defined by a vector.
The slow moving index is the leftmost with the index 0 and the fast moving index is dim() - 1u.
This is the same ordering you should keep in mind for alpaka buffers and multidimensional kernels later in the tutorial.
constexpr auto vec = Vec{2u, 3u}; float cArray2D[vec.y()][vec.x()] = {{1., 2., 3.}, {4., 5., 6.}}; for(uint32_t y = 0u; y < vec.y(); ++y) { for(uint32_t x = 0u; x < vec.x(); ++x) printf("%f ", cArray2D[y][x]); printf("\n"); }
Output:
1.000000 2.000000 3.000000 4.000000 5.000000 6.000000
You can perform typical arithmetic operations on vectors, e.g. +, -, …
Operations on a vector work element wise, except for == and !=.
constexpr concepts::CVector auto cvec0 = CVec<uint32_t, 1u, 2u, 3u>{}; constexpr concepts::CVector auto cvec1 = CVec<uint32_t, 13u, 17u, 19u>{}; // performing operations on a compile-time vector will result in a runtime vector type constexpr concepts::Vector auto vResult = cvec0 + cvec1; static_assert(vResult.z() == 14u && vResult.y() == 19u && vResult.x() == 22u);
A very useful function is the element permutation called swizzle. It returns a permuted copy of the original vector. The swizzle operator is using CVec, which will be shown later.
constexpr concepts::Vector auto cvec0 = CVec<uint32_t, 3u, 5u, 7u>{}; // permute the vector arguments constexpr concepts::Vector auto vResult = cvec0[CVec<uint32_t, 1u, 0u, 2u>{}]; // access components via names, order [z,y,x] static_assert(vResult.z() == 5u && vResult.y() == 3u && vResult.x() == 7u); // access components via operator[], order [0,1,...,dim-1]) static_assert(vResult[0] == 5u && vResult[1] == 3u && vResult[2] == 7u);
Sometimes it is useful to assign values only to a few components. The next example permutes the initial vector and broadcast-assigns a scalar to all selected components only. Note that you can only assign vectors with the same dimensionality and value type, or scalars those are lossless convertible.
concepts::Vector auto vec0 = Vec{3u, 5u, 7u}; // creates a permuted view to an existing vector to modify only a subset of components concepts::Vector auto vecView = vec0.ref(CVec<uint32_t, 0u, 2u>{}); static_assert(vecView.dim() == 2u); CHECK((vecView[0] == 3u && vecView[1] == 7u)); vecView = 42u; CHECK((vecView[0] == 42u && vecView[1] == 42u)); CHECK((vec0.z() == 42u && vec0.y() == 5u && vec0.x() == 42u)); CHECK((vec0[0] == 42u && vec0[1] == 5u && vec0[2] == 42u));
Since most vector operators work element wise, you need sometimes reduction methods like sum() or product() to accumulate all components to a single scalar value.
constexpr concepts::Vector<size_t> auto vec0 = Vec<size_t, 3u>{3llu, 5llu, 7llu}; // accumulate all elements of a vector static_assert(vec0.sum() == 15u); // same as above but supports all std functional operations static_assert(vec0.reduce(std::plus{}) == 15u); // All vector operations require that the lhs type is equal to the vector component type. // This is relaxed if the rhs or lhs of a vector can be up-casted without precision loss, or sign flips, and is a // scalar value. Operations with two vectors require equal value types. In this example `7u` is up-casted to // size_t. constexpr concepts::Vector auto vec1 = vec0 >= 7u; static_assert(vec1.z() == false && vec1.y() == false && vec1.x() == true); static_assert(vec1.reduce(std::logical_and{}) == false); // vector and scalar has the same precision Vec<uint64_t, 1> vec2 = Vec<uint64_t, 1>{42} * uint64_t{2}; // vector has a higher precession than the scalar // up-cast possible Vec<uint64_t, 1> vec3 = Vec<uint64_t, 1>{42} * uint32_t{2}; // vector has a lower precession than the scalar // does not compile because of precision loss // Vec<uint32_t, 1> vec4 = Vec<uint32_t, 1>{42} * uint64_t{2};
CVec
Vec is a vector whose component values are stored at runtime, while CVec stores the components in the template signature, which allows compile-time information to be propagated to called functions and used there at compile time.
CVec behaves like Vec and follows concepts::Vector but keeps the values available at compile time even if you pass it to a non constexpr function.
The next code will show you that you can use static_assert() which would not be possible with Vec.
void foo(concepts::CVector<uint32_t> auto value) { static_assert(value.dim() == 3u); // this would fail if the vector values are not known fully at compile time static_assert(value.x() == 11u); static_assert(value.y() == 7u); static_assert(value.z() == 5u); }constexpr auto cvec = CVec<uint32_t, 5u, 7u, 11u>{}; // compile-time vectors can be passed as function arguments and preserve compile-time values foo(cvec);
If you call operators like + on a CVec variable, the result type will be Vec and it will not keep the results compile time available if you pass the result to a function.
In this example it can only be validated with static_assert() because the operation is marked constexpr.
constexpr auto cvec0 = CVec<uint32_t, 5u, 7u>{}; constexpr auto cvec1 = CVec<uint32_t, 37u, 35u>{}; constexpr Vec result = cvec0 + cvec1; static_assert(result[0] == result[1] && result[0] == 42u);
Complete Source File
010_vector.cpp
1/* Copyright 2025 René Widera
2 * SPDX-License-Identifier: ISC
3 */
4
5#include <alpaka/alpaka.hpp>
6
7#include <catch2/catch_test_macros.hpp>
8
9#include <cstdint>
10#include <functional>
11
12/** @file Operations shown here are mostly constexpr to simplify the validation by using static_assert() */
13
14using namespace alpaka;
15
16TEST_CASE("vector 1D", "[docs]")
17{
18 // create a one-dimensional vector of unsigned int
19 constexpr auto vec0 = Vec{3u};
20 // equal to
21 constexpr auto vec1 = Vec<uint32_t, 1u>{3u};
22 // a vector is not required to be constexpr
23 auto vec2 = Vec{3u};
24
25 static_assert(std::is_same_v<ALPAKA_TYPEOF(vec0), ALPAKA_TYPEOF(vec1)>);
26 alpaka::unused(vec0, vec1, vec2);
27
28 // explicit type conversion from double to unsigned int
29 constexpr auto vec3 = Vec<uint32_t, 1u>{3.0};
30 static_assert(std::is_same_v<ALPAKA_TYPEOF(vec0), ALPAKA_TYPEOF(vec3)>);
31
32 // check the number of components aka dimensions
33 auto vec4 = Vec<uint32_t, 1u>{42u};
34 static_assert(vec4.dim() == 1u);
35 alpaka::unused(vec3, vec4);
36}
37
38TEST_CASE("vector 3D", "[docs]")
39{
40 // create a three-dimensional vector of uint32_t
41 constexpr auto vec = Vec{5u, 7u, 11u};
42
43 // x is the fast moving index in cases where a vector is used to describe an index within an index space
44 static_assert(vec.x() == 11u);
45 static_assert(vec[2u] == 11u);
46
47 static_assert(vec.y() == 7u);
48 static_assert(vec[1u] == 7u);
49
50 static_assert(vec.z() == 5u);
51 static_assert(vec[0u] == 5u);
52
53 static_assert(vec.dim() == 3u);
54}
55
56TEST_CASE("CArray 2D iterate", "[docs]")
57{
58 constexpr auto vec = Vec{2u, 3u};
59 float cArray2D[vec.y()][vec.x()] = {{1., 2., 3.}, {4., 5., 6.}};
60
61 for(uint32_t y = 0u; y < vec.y(); ++y)
62 {
63 for(uint32_t x = 0u; x < vec.x(); ++x)
64 printf("%f ", cArray2D[y][x]);
65 printf("\n");
66 }
67}
68
69TEST_CASE("compile-time vector", "[docs]")
70{
71 // create a three-dimensional vector of uint32_t
72 // dimensionality is derived from the number of template parameters
73 constexpr auto cvec = CVec<uint32_t, 5u, 7u, 11u>{};
74
75 // x is the fast moving index in cases where a vector is used to describe an index within an index space
76 static_assert(cvec.x() == 11u);
77 static_assert(cvec[2u] == 11u);
78
79 static_assert(cvec.y() == 7u);
80 static_assert(cvec[1u] == 7u);
81
82 static_assert(cvec.z() == 5u);
83 static_assert(cvec[0u] == 5u);
84}
85
86void foo(concepts::CVector<uint32_t> auto value)
87{
88 static_assert(value.dim() == 3u);
89
90 // this would fail if the vector values are not known fully at compile time
91 static_assert(value.x() == 11u);
92 static_assert(value.y() == 7u);
93 static_assert(value.z() == 5u);
94}
95
96
97TEST_CASE("compile-time vector as function argument", "[docs]")
98{
99 constexpr auto cvec = CVec<uint32_t, 5u, 7u, 11u>{};
100
101 // compile-time vectors can be passed as function arguments and preserve compile-time values
102 foo(cvec);
103}
104
105TEST_CASE("compile-time vector operations", "[docs]")
106{
107 constexpr auto cvec0 = CVec<uint32_t, 5u, 7u>{};
108 constexpr auto cvec1 = CVec<uint32_t, 37u, 35u>{};
109
110 constexpr Vec result = cvec0 + cvec1;
111 static_assert(result[0] == result[1] && result[0] == 42u);
112}
113
114TEST_CASE("compile-time vector calculations", "[docs]")
115{
116 constexpr concepts::CVector auto cvec0 = CVec<uint32_t, 1u, 2u, 3u>{};
117 constexpr concepts::CVector auto cvec1 = CVec<uint32_t, 13u, 17u, 19u>{};
118
119 // performing operations on a compile-time vector will result in a runtime vector type
120 constexpr concepts::Vector auto vResult = cvec0 + cvec1;
121 static_assert(vResult.z() == 14u && vResult.y() == 19u && vResult.x() == 22u);
122}
123
124TEST_CASE("vector swizzle", "[docs]")
125{
126 constexpr concepts::Vector auto cvec0 = CVec<uint32_t, 3u, 5u, 7u>{};
127
128 // permute the vector arguments
129 constexpr concepts::Vector auto vResult = cvec0[CVec<uint32_t, 1u, 0u, 2u>{}];
130 // access components via names, order [z,y,x]
131 static_assert(vResult.z() == 5u && vResult.y() == 3u && vResult.x() == 7u);
132 // access components via operator[], order [0,1,...,dim-1])
133 static_assert(vResult[0] == 5u && vResult[1] == 3u && vResult[2] == 7u);
134}
135
136TEST_CASE("vector ref", "[docs]")
137{
138 concepts::Vector auto vec0 = Vec{3u, 5u, 7u};
139
140 // creates a permuted view to an existing vector to modify only a subset of components
141 concepts::Vector auto vecView = vec0.ref(CVec<uint32_t, 0u, 2u>{});
142 static_assert(vecView.dim() == 2u);
143 CHECK((vecView[0] == 3u && vecView[1] == 7u));
144 vecView = 42u;
145 CHECK((vecView[0] == 42u && vecView[1] == 42u));
146
147 CHECK((vec0.z() == 42u && vec0.y() == 5u && vec0.x() == 42u));
148 CHECK((vec0[0] == 42u && vec0[1] == 5u && vec0[2] == 42u));
149}
150
151TEST_CASE("vector operations", "[docs]")
152{
153 constexpr concepts::Vector<size_t> auto vec0 = Vec<size_t, 3u>{3llu, 5llu, 7llu};
154
155 // accumulate all elements of a vector
156 static_assert(vec0.sum() == 15u);
157 // same as above but supports all std functional operations
158 static_assert(vec0.reduce(std::plus{}) == 15u);
159
160 // All vector operations require that the lhs type is equal to the vector component type.
161 // This is relaxed if the rhs or lhs of a vector can be up-casted without precision loss, or sign flips, and is a
162 // scalar value. Operations with two vectors require equal value types. In this example `7u` is up-casted to
163 // size_t.
164 constexpr concepts::Vector auto vec1 = vec0 >= 7u;
165 static_assert(vec1.z() == false && vec1.y() == false && vec1.x() == true);
166 static_assert(vec1.reduce(std::logical_and{}) == false);
167
168 // vector and scalar has the same precision
169 Vec<uint64_t, 1> vec2 = Vec<uint64_t, 1>{42} * uint64_t{2};
170 // vector has a higher precession than the scalar
171 // up-cast possible
172 Vec<uint64_t, 1> vec3 = Vec<uint64_t, 1>{42} * uint32_t{2};
173 // vector has a lower precession than the scalar
174 // does not compile because of precision loss
175 // Vec<uint32_t, 1> vec4 = Vec<uint32_t, 1>{42} * uint64_t{2};
176 CHECK(vec2 == Vec<uint64_t, 1>{84});
177 CHECK(vec3 == Vec<uint64_t, 1>{84});
178}
179
180TEST_CASE("simd", "[docs]")
181{
182 // Vectors are designed for integral types and index and extent calculations and definitions.
183 constexpr concepts::Vector<size_t> auto vec0 = Vec<size_t, 4u>{3llu, 5llu, 7llu, 11llu};
184 // If vectors are required for user data, you should use Simd instead.
185 // For integral and floating point types. Simd vectors are aligned and typical therefore faster to load from
186 // memory.
187 concepts::Simd auto simd0 = Simd<size_t, 4u>{3llu, 5llu, 7llu, 11llu};
188
189 // compare component wise
190 alpaka::apply([&](auto const&... idx) { CHECK(((vec0[idx] == simd0[idx]) && ...)); }, iotaCVec<int, 4u>());
191}