Sync HIP documentation 2025-10-20 (#1258)

* Add examples to tools folder
* Correct P2P memory access section
* Sync poriting guide
* Add HIP Graph tutorial
* Add hint about using amdgpu-dkms for IPC API
* Add a few more env variables
This commit is contained in:
Istvan Kiss
2025-10-29 07:42:06 +01:00
کامیت شده توسط GitHub
والد 8e98b80deb
کامیت 197f73dac9
89فایلهای تغییر یافته به همراه10327 افزوده شده و 3486 حذف شده
@@ -0,0 +1,95 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#include <numeric>
#include <vector>
///\brief Calculates \p a[i] = \p a[i] + \p b[i] where \p i stands for the thread's index in the grid.
// [sphinx-kernel-start]
__global__ void AddKernel(float* a, const float* b)
{
int global_idx = threadIdx.x + blockIdx.x * blockDim.x;
a[global_idx] += b[global_idx];
}
// [sphinx-kernel-end]
int main()
{
// The number of float elements in each vector.
constexpr unsigned int size = 1 << 20; // == 1'048'576 elements
// Bytes to allocate for each device vector.
constexpr size_t size_bytes = size * sizeof(float);
// Number of threads per kernel block.
constexpr unsigned int threads_per_block = 256;
// Number of blocks per kernel grid. The expression below calculates ceil(size/block_size).
constexpr unsigned int number_of_blocks = ceiling_div(size, threads_per_block);
// Allocate a vector and fill it with an increasing sequence (i.e. 1, 2, 3, 4...)
std::vector<float> h_a(size);
std::iota(h_a.begin(), h_a.end(), 1.f);
// Allocate b vector and fill it with a decreasing sequence (i.e. 1'048'576, 1'048'575, ..., 3, 2, 1)
std::vector<float> h_b(size);
std::iota(h_b.rbegin(), h_b.rend(), 1.f);
// Allocate and copy vectors to device memory.
float* d_a{};
float* d_b{};
HIP_CHECK(hipMalloc(&d_a, size_bytes));
HIP_CHECK(hipMalloc(&d_b, size_bytes));
HIP_CHECK(hipMemcpy(d_a, h_a.data(), size_bytes, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(d_b, h_b.data(), size_bytes, hipMemcpyHostToDevice));
std::cout << "Calculating a[i] = a[i] + b[i] over " << size << " elements." << std::endl;
// Launch the kernel on the default stream.
// [sphinx-kernel-launch-start]
AddKernel<<<number_of_blocks, threads_per_block>>>(d_a, d_b);
// [sphinx-kernel-launch-end]
// Check if the kernel launch was successful.
HIP_CHECK(hipGetLastError());
// Copy the results back to the host. This call blocks the host's execution until the copy is finished.
HIP_CHECK(hipMemcpy(h_a.data(), d_a, size_bytes, hipMemcpyDeviceToHost));
// Free device memory.
HIP_CHECK(hipFree(d_b));
HIP_CHECK(hipFree(d_a));
// Print the first few elements of the results:
constexpr size_t elements_to_print = 10;
std::cout << "First " << elements_to_print << " elements of the results: "
<< format_range(h_a.begin(), h_a.begin() + elements_to_print) << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,142 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
// GPU Kernels
__global__ void kernelA(double* arrayA, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] += 1.0;
}
}
__global__ void kernelB(double* arrayA, double* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayB[x] += arrayA[x] + 3.0;
}
}
int main()
{
constexpr int numOfBlocks = 1 << 20;
constexpr int threadsPerBlock = 1024;
constexpr int numberOfIterations = 50;
// The array size smaller to avoid the relatively short kernel launch compared to memory copies
constexpr std::size_t arraySize = 1U << 25;
double *d_dataA;
double *d_dataB;
double initValueA = 0.0;
double initValueB = 2.0;
std::vector<double> vectorA(arraySize, initValueA);
std::vector<double> vectorB(arraySize, initValueB);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_dataA, arraySize * sizeof(*d_dataA)));
HIP_CHECK(hipMalloc(&d_dataB, arraySize * sizeof(*d_dataB)));
// Create streams
hipStream_t streamA, streamB;
HIP_CHECK(hipStreamCreate(&streamA));
HIP_CHECK(hipStreamCreate(&streamB));
for(unsigned int iteration = 0; iteration < numberOfIterations; iteration++)
{
// Stream 1: Host to Device 1
HIP_CHECK(hipMemcpyAsync(d_dataA, vectorA.data(), arraySize * sizeof(*d_dataA), hipMemcpyHostToDevice, streamA));
// Stream 2: Host to Device 2
HIP_CHECK(hipMemcpyAsync(d_dataB, vectorB.data(), arraySize * sizeof(*d_dataB), hipMemcpyHostToDevice, streamB));
// Stream 1: Kernel 1
kernelA<<<numOfBlocks, threadsPerBlock, 0, streamA>>>(d_dataA, arraySize);
// Wait for streamA finish
HIP_CHECK(hipStreamSynchronize(streamA));
// Stream 2: Kernel 2
kernelB<<<numOfBlocks, threadsPerBlock, 0, streamB>>>(d_dataA, d_dataB, arraySize);
// Stream 1: Device to Host 2 (after Kernel 1)
HIP_CHECK(hipMemcpyAsync(vectorA.data(), d_dataA, arraySize * sizeof(*vectorA.data()), hipMemcpyDeviceToHost, streamA));
// Stream 2: Device to Host 2 (after Kernel 2)
HIP_CHECK(hipMemcpyAsync(vectorB.data(), d_dataB, arraySize * sizeof(*vectorB.data()), hipMemcpyDeviceToHost, streamB));
}
// Wait for all operations in both streams to complete
HIP_CHECK(hipStreamSynchronize(streamA));
HIP_CHECK(hipStreamSynchronize(streamB));
// Verify results
double expectedA = (double)numberOfIterations;
double expectedB = initValueB + (3.0 * numberOfIterations) + (expectedA * (expectedA + 1.0)) / 2.0;
bool passed = true;
for(std::size_t i = 0; i < arraySize; ++i)
{
if(vectorA[i] != expectedA)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedA << " got " << vectorA[i] << " at index: " << i << std::endl;
break;
}
if(vectorB[i] != expectedB)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedB << " got " << vectorB[i] << " at index: " << i << std::endl;
break;
}
}
if(passed)
{
std::cout << "Asynchronous execution completed successfully." << std::endl;
}
else
{
std::cerr << "Asynchronous execution failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipStreamDestroy(streamA));
HIP_CHECK(hipStreamDestroy(streamB));
HIP_CHECK(hipFree(d_dataA));
HIP_CHECK(hipFree(d_dataB));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,110 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <cuda_runtime.h>
#include <iostream>
#include <vector>
__global__ void block_reduction(const float* input, float* output, int num_elements)
{
extern __shared__ float s_data[];
int tid = threadIdx.x;
int global_id = blockDim.x * blockIdx.x + tid;
if (global_id < num_elements)
{
s_data[tid] = input[global_id];
}
else
{
s_data[tid] = 0.0f;
}
__syncthreads();
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1)
{
if (tid < stride)
{
s_data[tid] += s_data[tid + stride];
}
__syncthreads();
}
if (tid == 0)
{
output[blockIdx.x] = s_data[0];
}
}
int main()
{
int threads = 256;
const int num_elements = 50000;
std::vector<float> h_a(num_elements);
std::vector<float> h_b((num_elements + threads - 1) / threads);
for (int i = 0; i < num_elements; ++i)
{
h_a[i] = rand() / static_cast<float>(RAND_MAX);
}
float *d_a, *d_b;
cudaMalloc(&d_a, h_a.size() * sizeof(float));
cudaMalloc(&d_b, h_b.size() * sizeof(float));
cudaStream_t stream;
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
cudaEvent_t start_event, stop_event;
cudaEventCreate(&start_event);
cudaEventCreate(&stop_event);
cudaMemcpyAsync(d_a, h_a.data(), h_a.size() * sizeof(float), cudaMemcpyHostToDevice, stream);
cudaEventRecord(start_event, stream);
int blocks = (num_elements + threads - 1) / threads;
block_reduction<<<blocks, threads, threads * sizeof(float), stream>>>(d_a, d_b, num_elements);
cudaMemcpyAsync(h_b.data(), d_b, h_b.size() * sizeof(float), cudaMemcpyDeviceToHost, stream);
cudaEventRecord(stop_event, stream);
cudaEventSynchronize(stop_event);
float milliseconds = 0.f;
cudaEventElapsedTime(&milliseconds, start_event, stop_event);
std::cout << "Kernel execution time: " << milliseconds << " ms\n";
cudaFree(d_a);
cudaFree(d_b);
cudaEventDestroy(start_event);
cudaEventDestroy(stop_event);
cudaStreamDestroy(stream);
return 0;
}
// [sphinx-end]
@@ -0,0 +1,58 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
int main()
{
std::size_t stackSize;
HIP_CHECK(hipDeviceGetLimit(&stackSize, hipLimitStackSize));
std::cout << "Default stack size: " << stackSize << " bytes" << std::endl;
// Set a new stack size
std::size_t newStackSize = 1024 * 8; // 8 KiB
HIP_CHECK(hipDeviceSetLimit(hipLimitStackSize, newStackSize));
HIP_CHECK(hipDeviceGetLimit(&stackSize, hipLimitStackSize));
std::cout << "Updated stack size: " << stackSize << " bytes" << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,89 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " << hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Performs a simple initialization of an array with the thread's index variables.
// This function is only available in device code.
__device__ void init_array(float * const a, const unsigned int arraySize)
{
// globalIdx uniquely identifies a thread in a 1D launch configuration.
const int globalIdx = threadIdx.x + blockIdx.x * blockDim.x;
// Each thread initializes a single element of the array.
if(globalIdx < arraySize)
{
a[globalIdx] = globalIdx;
}
}
// Rounds a value up to the next multiple.
// This function is available in host and device code.
__host__ __device__ constexpr int round_up_to_nearest_multiple(int number, int multiple)
{
return (number + multiple - 1)/multiple;
}
__global__ void example_kernel(float * const a, const unsigned int N)
{
// Initialize array.
init_array(a, N);
// Perform additional work:
// - work with the array
// - use the array in a different kernel
// - ...
}
int main()
{
constexpr int N = 100000000; // problem size
constexpr int blockSize = 256; //configurable block size
//needed number of blocks for the given problem size
constexpr int gridSize = round_up_to_nearest_multiple(N, blockSize);
float *a;
// allocate memory on the GPU
HIP_CHECK(hipMalloc(&a, sizeof(*a) * N));
std::cout << "Launching kernel." << std::endl;
example_kernel<<<dim3(gridSize), dim3(blockSize), 0/*example doesn't use shared memory*/, 0/*default stream*/>>>(a, N);
// make sure kernel execution is finished by synchronizing. The CPU can also
// execute other instructions during that time
HIP_CHECK(hipDeviceSynchronize());
std::cout << "Kernel execution finished." << std::endl;
HIP_CHECK(hipFree(a));
}
// [sphinx-end]
@@ -0,0 +1,165 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
// source code for hiprtc
static constexpr auto kernel_source{
R"(
extern "C"
__global__ void vector_add(float* output, float* input1, float* input2, size_t size)
{
int i = threadIdx.x;
if (i < size)
{
output[i] = input1[i] + input2[i];
}
}
)"};
int main()
{
hiprtcProgram prog;
auto rtc_ret_code = hiprtcCreateProgram(&prog, // HIPRTC program handle
kernel_source, // kernel source string
"vector_add.cpp", // Name of the file
0, // Number of headers
nullptr, // Header sources
nullptr); // Name of header file
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
hipDeviceProp_t props;
int device = 0;
HIP_CHECK(hipGetDeviceProperties(&props, device));
auto sarg = std::string{"--gpu-architecture="} + props.gcnArchName; // device for which binary is to be generated
const char* options[] = {sarg.c_str()};
rtc_ret_code = hiprtcCompileProgram(prog, // hiprtcProgram
1, // Number of options
options); // Clang Options
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
std::size_t logSize;
HIPRTC_CHECK(hiprtcGetProgramLogSize(prog, &logSize));
if (logSize)
{
std::string log(logSize, '\0');
HIPRTC_CHECK(hiprtcGetProgramLog(prog, &log[0]));
std::cerr << "Compilation failed or produced warnings: " << log << std::endl;
std::abort();
}
std::size_t codeSize;
HIPRTC_CHECK(hiprtcGetCodeSize(prog, &codeSize));
std::vector<char> kernel_binary(codeSize);
HIPRTC_CHECK(hiprtcGetCode(prog, kernel_binary.data()));
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
hipModule_t module;
hipFunction_t kernel;
HIP_CHECK(hipModuleLoadData(&module, kernel_binary.data()));
HIP_CHECK(hipModuleGetFunction(&kernel, module, "vector_add"));
constexpr std::size_t ele_size = 256; // total number of items to add
std::vector<float> hinput, output;
hinput.reserve(ele_size);
output.reserve(ele_size);
for (std::size_t i = 0; i < ele_size; i++)
{
hinput.push_back(static_cast<float>(i + 1));
output.push_back(0.0f);
}
float *dinput1, *dinput2, *doutput;
HIP_CHECK(hipMalloc(&dinput1, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&dinput2, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&doutput, sizeof(float) * ele_size));
HIP_CHECK(hipMemcpy(dinput1, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dinput2, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
struct
{
float* output;
float* input1;
float* input2;
std::size_t size;
} args{doutput, dinput1, dinput2, ele_size};
auto size = sizeof(args);
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, &args, HIP_LAUNCH_PARAM_BUFFER_SIZE, &size,
HIP_LAUNCH_PARAM_END};
HIP_CHECK(hipModuleLaunchKernel(kernel, 1, 1, 1, ele_size, 1, 1, 0, nullptr, nullptr, config));
HIP_CHECK(hipMemcpy(output.data(), doutput, sizeof(float) * ele_size, hipMemcpyDeviceToHost));
for (std::size_t i = 0; i < ele_size; i++)
{
if ((hinput[i] + hinput[i]) != output[i])
{
std::cout << "Failed in validation: " << (hinput[i] + hinput[i]) << " - " << output[i] << std::endl;
std::abort();
}
}
std::cout << "Passed" << std::endl;
HIP_CHECK(hipFree(dinput1));
HIP_CHECK(hipFree(dinput2));
HIP_CHECK(hipFree(doutput));
return EXIT_SUCCESS;
}
// [sphinx-stop]
@@ -0,0 +1,142 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <hip/hip_complex.h>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) { \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
exit(EXIT_FAILURE); \
} \
}
// Kernel to compute DFT
__global__ void computeDFT(const float* input, hipFloatComplex* output, const int N)
{
int k = blockIdx.x * blockDim.x + threadIdx.x;
if (k >= N) return;
hipFloatComplex sum = make_hipFloatComplex(0.0f, 0.0f);
for (int n = 0; n < N; n++)
{
float angle = -2.0f * M_PI * k * n / N;
hipFloatComplex w = make_hipFloatComplex(cosf(angle), sinf(angle));
hipFloatComplex x = make_hipFloatComplex(input[n], 0.0f);
sum = hipCaddf(sum, hipCmulf(x, w));
}
output[k] = sum;
}
// CPU implementation of DFT for verification
std::vector<hipFloatComplex> cpuDFT(const std::vector<float>& input)
{
const int N = input.size();
std::vector<hipFloatComplex> result(N);
for (int k = 0; k < N; k++)
{
hipFloatComplex sum = make_hipFloatComplex(0.0f, 0.0f);
for (int n = 0; n < N; n++)
{
float angle = -2.0f * M_PI * k * n / N;
hipFloatComplex w = make_hipFloatComplex(cosf(angle), sinf(angle));
hipFloatComplex x = make_hipFloatComplex(input[n], 0.0f);
sum = hipCaddf(sum, hipCmulf(x, w));
}
result[k] = sum;
}
return result;
}
int main()
{
const int N = 256; // Signal length
const int blockSize = 256;
// Generate input signal: sum of two sine waves
std::vector<float> signal(N);
for (int i = 0; i < N; i++)
{
float t = static_cast<float>(i) / N;
signal[i] = sinf(2.0f * M_PI * 10.0f * t) + // 10 Hz component
0.5f * sinf(2.0f * M_PI * 20.0f * t); // 20 Hz component
}
// Compute reference solution on CPU
std::vector<hipFloatComplex> cpu_output = cpuDFT(signal);
// Allocate device memory
float* d_signal;
hipFloatComplex* d_output;
HIP_CHECK(hipMalloc(&d_signal, N * sizeof(float)));
HIP_CHECK(hipMalloc(&d_output, N * sizeof(hipFloatComplex)));
// Copy input to device
HIP_CHECK(hipMemcpy(d_signal, signal.data(), N * sizeof(float), hipMemcpyHostToDevice));
// Launch kernel
dim3 grid((N + blockSize - 1) / blockSize);
dim3 block(blockSize);
computeDFT<<<grid, block>>>(d_signal, d_output, N);
HIP_CHECK(hipGetLastError());
// Get GPU results
std::vector<hipFloatComplex> gpu_output(N);
HIP_CHECK(hipMemcpy(gpu_output.data(), d_output, N * sizeof(hipFloatComplex), hipMemcpyDeviceToHost));
// Verify results
bool passed = true;
const float tolerance = 1e-5f; // Adjust based on precision requirements
for (int i = 0; i < N; i++)
{
float diff_real = std::abs(hipCrealf(gpu_output[i]) - hipCrealf(cpu_output[i]));
float diff_imag = std::abs(hipCimagf(gpu_output[i]) - hipCimagf(cpu_output[i]));
if (diff_real > tolerance || diff_imag > tolerance)
{
passed = false;
break;
}
}
std::cout << "DFT Verification: " << (passed ? "PASSED" : "FAILED") << "\n";
// Cleanup
HIP_CHECK(hipFree(d_signal));
HIP_CHECK(hipFree(d_output));
return passed ? EXIT_SUCCESS : EXIT_FAILURE;
}
// [sphinx-end]
@@ -0,0 +1,75 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
// [sphinx-start]
constexpr std::size_t const_array_size = 32;
__constant__ double const_array[const_array_size];
void set_constant_memory(double* values)
{
HIP_CHECK(hipMemcpyToSymbol(const_array, values, const_array_size * sizeof(double)));
}
__global__ void kernel_using_const_memory(double* array)
{
int warpIdx = threadIdx.x / warpSize;
// uniform access of warps to const_array for best performance
array[blockIdx.x] *= const_array[warpIdx];
}
// [sphinx-end]
int main()
{
std::size_t elements = 32;
std::size_t size_bytes = elements * sizeof(double);
// allocate host array
double *host_array = new double[elements];
// allocate device array
double *device_array = nullptr;
HIP_CHECK(hipMalloc((double**) &device_array, size_bytes));
// copy from host to the device
set_constant_memory(host_array);
kernel_using_const_memory<<<32, 32>>>(device_array);
// copy from device to host, to e.g. get results from the kernel
HIP_CHECK(hipMemcpy(host_array, device_array, size_bytes, hipMemcpyDeviceToHost));
// free memory when not needed any more
HIP_CHECK(hipFree(device_array));
delete[] host_array;
std::cout << "Success!" << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,84 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main()
{
int *a, *b, *c;
int deviceId;
HIP_CHECK(hipGetDevice(&deviceId)); // Get the current device ID
// Allocate memory for a, b and c that is accessible to both device and host codes.
HIP_CHECK(hipMallocManaged(&a, sizeof(*a)));
HIP_CHECK(hipMallocManaged(&b, sizeof(*b)));
HIP_CHECK(hipMallocManaged(&c, sizeof(*c)));
// Setup input values.
*a = 1;
*b = 2;
// Prefetch the data to the GPU device.
HIP_CHECK(hipMemPrefetchAsync(a, sizeof(*a), deviceId, 0));
HIP_CHECK(hipMemPrefetchAsync(b, sizeof(*b), deviceId, 0));
HIP_CHECK(hipMemPrefetchAsync(c, sizeof(*c), deviceId, 0));
// Launch add() kernel on GPU.
add<<<1, 1>>>(a, b, c);
// Prefetch the result back to the CPU.
HIP_CHECK(hipMemPrefetchAsync(c, sizeof(*c), hipCpuDeviceId, 0));
// Wait for the prefetch operations to complete.
HIP_CHECK(hipDeviceSynchronize());
// Prints the result.
std::cout << *a << " + " << *b << " = " << *c << std::endl;
// Cleanup allocated memory.
HIP_CHECK(hipFree(a));
HIP_CHECK(hipFree(b));
HIP_CHECK(hipFree(c));
return 0;
}
// [sphinx-end]
@@ -0,0 +1,61 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__global__ void test_kernel()
{
// [sphinx-start]
//#if __CUDA_ARCH__ >= 130 // does not properly specify, what feature is required, not portable
#if __HIP_ARCH_HAS_DOUBLES__ == 1 // explicitly specifies, what feature is required, portable between AMD and NVIDIA GPUs
// device code
#endif
// [sphinx-end]
#if __HIP_ARCH_HAS_DOUBLES__ == 1
printf("Device has double-precision support.\n");
#else
printf("Device does not have double-precision support.\n");
#endif
}
int main()
{
test_kernel<<<1, 1, 0, 0>>>();
HIP_CHECK(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
@@ -0,0 +1,74 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
std::cout << "Number of devices: " << deviceCount << std::endl;
for (int deviceId = 0; deviceId < deviceCount; ++deviceId)
{
hipDeviceProp_t deviceProp;
HIP_CHECK(hipGetDeviceProperties(&deviceProp, deviceId));
std::cout << "Device " << deviceId << std::endl << " Properties:" << std::endl;
std::cout << " Name: " << deviceProp.name << std::endl;
std::cout << " Total Global Memory: " << deviceProp.totalGlobalMem / (1024 * 1024) << " MiB" << std::endl;
std::cout << " Shared Memory per Block: " << deviceProp.sharedMemPerBlock / 1024 << " KiB" << std::endl;
std::cout << " Registers per Block: " << deviceProp.regsPerBlock << std::endl;
std::cout << " Warp Size: " << deviceProp.warpSize << std::endl;
std::cout << " Max Threads per Block: " << deviceProp.maxThreadsPerBlock << std::endl;
std::cout << " Max Threads per Multiprocessor: " << deviceProp.maxThreadsPerMultiProcessor << std::endl;
std::cout << " Number of Multiprocessors: " << deviceProp.multiProcessorCount << std::endl;
std::cout << " Max Threads Dimensions: ["
<< deviceProp.maxThreadsDim[0] << ", "
<< deviceProp.maxThreadsDim[1] << ", "
<< deviceProp.maxThreadsDim[2] << "]" << std::endl;
std::cout << " Max Grid Size: ["
<< deviceProp.maxGridSize[0] << ", "
<< deviceProp.maxGridSize[1] << ", "
<< deviceProp.maxGridSize[2] << "]" << std::endl;
std::cout << std::endl;
}
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,72 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
__device__ unsigned long long fibonacci(unsigned long long n)
{
if (n == 0 || n == 1)
{
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
__global__ void kernel(unsigned long long n)
{
unsigned long long result = fibonacci(n);
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if (x == 0)
printf("%llu! = %llu \n", n, result);
}
int main()
{
kernel<<<1, 1>>>(10);
HIP_CHECK(hipDeviceSynchronize());
// With -O0 optimization option hit the stack limit
// kernel<<<1, 256>>>(2048);
// HIP_CHECK(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,100 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__global__ void simpleKernel(double *data, std::size_t elems)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if(idx < elems)
data[idx] = idx * 2.0;
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
if(deviceCount < 2)
{
std::cout << "This example requires at least two HIP devices." << std::endl;
return EXIT_SUCCESS;
}
double* deviceData0;
double* deviceData1;
constexpr std::size_t elems = 1024;
constexpr std::size_t size = elems * sizeof(double);
int deviceId0 = 0;
int deviceId1 = 1;
// Set device 0 and perform operations
HIP_CHECK(hipSetDevice(deviceId0)); // Set device 0 as current
HIP_CHECK(hipMalloc(&deviceData0, size)); // Allocate memory on device 0
simpleKernel<<<8, 128>>>(deviceData0, elems); // Launch kernel on device 0
HIP_CHECK(hipDeviceSynchronize());
// Set device 1 and perform operations
HIP_CHECK(hipSetDevice(deviceId1)); // Set device 1 as current
HIP_CHECK(hipMalloc(&deviceData1, size)); // Allocate memory on device 1
simpleKernel<<<8, 128>>>(deviceData1, elems); // Launch kernel on device 1
HIP_CHECK(hipDeviceSynchronize());
// Copy result from device 0
double hostData0[elems];
HIP_CHECK(hipSetDevice(deviceId0));
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
// Copy result from device 1
double hostData1[elems];
HIP_CHECK(hipSetDevice(deviceId1));
HIP_CHECK(hipMemcpy(hostData1, deviceData1, size, hipMemcpyDeviceToHost));
// Display results from both devices
std::cout << "Device 0 data: " << hostData0[0] << std::endl;
std::cout << "Device 1 data: " << hostData1[0] << std::endl;
// Free device memory
HIP_CHECK(hipFree(deviceData0));
HIP_CHECK(hipFree(deviceData1));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,64 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
// [sphinx-start]
extern __shared__ int dynamic_shared[];
__global__ void kernel(int array1SizeX, int array1SizeY, int array2Size)
{
// at least (array1SizeX * array1SizeY + array2Size) * sizeof(int) bytes
// dynamic shared memory need to be allocated when the kernel is launched
int* array1 = dynamic_shared;
// array1 is interpreted as 2D of size:
int array1Size = array1SizeX * array1SizeY;
int* array2 = &(array1[array1Size]);
if(threadIdx.x < array1SizeX && threadIdx.y < array1SizeY)
{
// access array1 with threadIdx.x + threadIdx.y * array1SizeX
}
if(threadIdx.x < array2Size)
{
// access array2 threadIdx.x
}
}
// [sphinx-end]
int main()
{
std::size_t shared_memory_bytes = 512 * sizeof(int);
kernel<<<64, 512, shared_memory_bytes>>>(512, 1, 512);
HIP_CHECK(hipPeekAtLastError());
HIP_CHECK(hipDeviceSynchronize());
std::cout << "Success!" << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,74 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main()
{
int *a, *b, *c;
// Allocate memory for a, b and c that is accessible to both device and host codes.
HIP_CHECK(hipMallocManaged(&a, sizeof(*a)));
HIP_CHECK(hipMallocManaged(&b, sizeof(*b)));
HIP_CHECK(hipMallocManaged(&c, sizeof(*c)));
// Setup input values.
*a = 1;
*b = 2;
// Launch add() kernel on GPU.
add<<<1, 1>>>(a, b, c);
// Wait for GPU to finish before accessing on host.
HIP_CHECK(hipDeviceSynchronize());
// Print the result.
std::cout << *a << " + " << *b << " = " << *c << std::endl;
// Cleanup allocated memory.
HIP_CHECK(hipFree(a));
HIP_CHECK(hipFree(b));
HIP_CHECK(hipFree(c));
return 0;
}
// [sphinx-end]
@@ -0,0 +1,97 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c, std::size_t size)
{
const std::size_t index = threadIdx.x + blockDim.x * blockIdx.x;
if(index < size)
{
c[index] += a[index] + b[index];
}
}
int main()
{
constexpr int numOfBlocks = 256;
constexpr int threadsPerBlock = 256;
constexpr std::size_t arraySize = 1U << 16;
std::vector<int> a(arraySize), b(arraySize), c(arraySize);
int *d_a, *d_b, *d_c;
// Setup input values.
std::fill(a.begin(), a.end(), 1);
std::fill(b.begin(), b.end(), 2);
// Allocate device copies of a, b and c.
HIP_CHECK(hipMalloc(&d_a, arraySize * sizeof(int)));
HIP_CHECK(hipMalloc(&d_b, arraySize * sizeof(int)));
HIP_CHECK(hipMalloc(&d_c, arraySize * sizeof(int)));
// Copy input values to device.
HIP_CHECK(hipMemcpy(d_a, a.data(), arraySize * sizeof(int), hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(d_b, b.data(), arraySize * sizeof(int), hipMemcpyHostToDevice));
// Launch add() kernel on GPU.
add<<<numOfBlocks, threadsPerBlock>>>(d_a, d_b, d_c, arraySize);
// Check the kernel launch
HIP_CHECK(hipGetLastError());
// Check for kernel execution error
HIP_CHECK(hipDeviceSynchronize());
// Copy the result back to the host.
HIP_CHECK(hipMemcpy(c.data(), d_c, arraySize * sizeof(int), hipMemcpyDeviceToHost));
// Cleanup allocated memory.
HIP_CHECK(hipFree(d_a));
HIP_CHECK(hipFree(d_b));
HIP_CHECK(hipFree(d_c));
// Print the result.
std::cout << a[0] << " + " << b[0] << " = " << c[0] << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,153 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
// GPU Kernels
__global__ void kernelA(double* arrayA, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] += 1.0;
}
}
__global__ void kernelB(double* arrayA, double* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayB[x] += arrayA[x] + 3.0;
}
}
int main()
{
constexpr int numOfBlocks = 1 << 20;
constexpr int threadsPerBlock = 1024;
constexpr int numberOfIterations = 50;
// The array size smaller to avoid the relatively short kernel launch compared to memory copies
constexpr std::size_t arraySize = 1U << 25;
double *d_dataA;
double *d_dataB;
double initValueA = 0.0;
double initValueB = 2.0;
std::vector<double> vectorA(arraySize, initValueA);
std::vector<double> vectorB(arraySize, initValueB);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_dataA, arraySize * sizeof(*d_dataA)));
HIP_CHECK(hipMalloc(&d_dataB, arraySize * sizeof(*d_dataB)));
// Create streams
hipStream_t streamA, streamB;
HIP_CHECK(hipStreamCreate(&streamA));
HIP_CHECK(hipStreamCreate(&streamB));
// Create events
hipEvent_t event, eventA, eventB;
HIP_CHECK(hipEventCreate(&event));
HIP_CHECK(hipEventCreate(&eventA));
HIP_CHECK(hipEventCreate(&eventB));
for(unsigned int iteration = 0; iteration < numberOfIterations; iteration++)
{
// Stream 1: Host to Device 1
HIP_CHECK(hipMemcpyAsync(d_dataA, vectorA.data(), arraySize * sizeof(*d_dataA), hipMemcpyHostToDevice, streamA));
// Stream 2: Host to Device 2
HIP_CHECK(hipMemcpyAsync(d_dataB, vectorB.data(), arraySize * sizeof(*d_dataB), hipMemcpyHostToDevice, streamB));
// Stream 1: Kernel 1
kernelA<<<numOfBlocks, threadsPerBlock, 0, streamA>>>(d_dataA, arraySize);
// Record event after the GPU kernel in Stream 1
HIP_CHECK(hipEventRecord(event, streamA));
// Stream 2: Wait for event before starting Kernel 2
HIP_CHECK(hipStreamWaitEvent(streamB, event, 0));
// Stream 2: Kernel 2
kernelB<<<numOfBlocks, threadsPerBlock, 0, streamB>>>(d_dataA, d_dataB, arraySize);
// Stream 1: Device to Host 2 (after Kernel 1)
HIP_CHECK(hipMemcpyAsync(vectorA.data(), d_dataA, arraySize * sizeof(*vectorA.data()), hipMemcpyDeviceToHost, streamA));
// Stream 2: Device to Host 2 (after Kernel 2)
HIP_CHECK(hipMemcpyAsync(vectorB.data(), d_dataB, arraySize * sizeof(*vectorB.data()), hipMemcpyDeviceToHost, streamB));
// Wait for all operations in both streams to complete
HIP_CHECK(hipEventRecord(eventA, streamA));
HIP_CHECK(hipEventRecord(eventB, streamB));
HIP_CHECK(hipStreamWaitEvent(streamA, eventA, 0));
HIP_CHECK(hipStreamWaitEvent(streamB, eventB, 0));
}
// Verify results
double expectedA = (double)numberOfIterations;
double expectedB = initValueB + (3.0 * numberOfIterations) + (expectedA * (expectedA + 1.0)) / 2.0;
bool passed = true;
for(std::size_t i = 0; i < arraySize; ++i)
{
if(vectorA[i] != expectedA)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedA << " got " << vectorA[i] << std::endl;
break;
}
if(vectorB[i] != expectedB)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedB << " got " << vectorB[i] << std::endl;
break;
}
}
if(passed)
{
std::cout << "Asynchronous execution with events completed successfully." << std::endl;
}
else
{
std::cerr << "Asynchronous execution with events failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipEventDestroy(event));
HIP_CHECK(hipEventDestroy(eventA));
HIP_CHECK(hipEventDestroy(eventB));
HIP_CHECK(hipStreamDestroy(streamA));
HIP_CHECK(hipStreamDestroy(streamB));
HIP_CHECK(hipFree(d_dataA));
HIP_CHECK(hipFree(d_dataB));
return 0;
}
// [sphinx-end]
@@ -0,0 +1,58 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
int main()
{
// [sphinx-start]
std::size_t elements = 1 << 20;
std::size_t size_bytes = elements * sizeof(int);
// allocate host and device memory
int *host_pointer = new int[elements];
int *device_input, *device_result;
HIP_CHECK(hipMalloc(&device_input, size_bytes));
HIP_CHECK(hipMalloc(&device_result, size_bytes));
// copy from host to the device
HIP_CHECK(hipMemcpy(device_input, host_pointer, size_bytes, hipMemcpyHostToDevice));
// Use memory on the device, i.e. execute kernels
// copy from device to host, to e.g. get results from the kernel
HIP_CHECK(hipMemcpy(host_pointer, device_result, size_bytes, hipMemcpyDeviceToHost));
// free memory when not needed any more
HIP_CHECK(hipFree(device_result));
HIP_CHECK(hipFree(device_input));
delete[] host_pointer;
// [sphinx-end]
return EXIT_SUCCESS;
}
@@ -0,0 +1,79 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main()
{
int a, b, c;
int *d_a, *d_b, *d_c;
// Setup input values.
a = 1;
b = 2;
// Allocate device copies of a, b and c.
HIP_CHECK(hipMalloc(&d_a, sizeof(*d_a)));
HIP_CHECK(hipMalloc(&d_b, sizeof(*d_b)));
HIP_CHECK(hipMalloc(&d_c, sizeof(*d_c)));
// Copy input values to device.
HIP_CHECK(hipMemcpy(d_a, &a, sizeof(*d_a), hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(d_b, &b, sizeof(*d_b), hipMemcpyHostToDevice));
// Launch add() kernel on GPU.
add<<<1, 1>>>(d_a, d_b, d_c);
// Copy the result back to the host.
HIP_CHECK(hipMemcpy(&c, d_c, sizeof(*d_c), hipMemcpyDeviceToHost));
// Cleanup allocated memory.
HIP_CHECK(hipFree(d_a));
HIP_CHECK(hipFree(d_b));
HIP_CHECK(hipFree(d_c));
// Prints the result.
std::cout << a << " + " << b << " = " << c << std::endl;
return 0;
}
// [sphinx-end]
@@ -0,0 +1,53 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
extern __shared__ int shared_array[];
__global__ void kernel()
{
// initialize shared memory
shared_array[threadIdx.x] = threadIdx.x;
// use shared memory - synchronize to make sure, that all threads of the
// block see all changes to shared memory
__syncthreads();
}
int main()
{
//shared memory in this case depends on the configurable block size
constexpr int blockSize = 256;
constexpr int sharedMemSize = blockSize * sizeof(int);
constexpr int gridSize = 2;
kernel<<<dim3(gridSize), dim3(blockSize), sharedMemSize, 0>>>();
if(auto err = hipDeviceSynchronize(); err != hipSuccess)
std::cerr << "HIP error " << err << ": " << hipGetErrorString(err) << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,606 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "backprojection.hpp"
#include "filtering.hpp"
#include "log_transform.hpp"
#include "normalization.hpp"
#include "phantom.hpp"
#include "projection.hpp"
#include "utility.hpp"
#include "weighting.hpp"
#include "volume.hpp"
#include <hip/hip_runtime.h>
#include <hipfft/hipfft.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <numbers>
#include <ostream>
#include <set>
#include <stdexcept>
#include <vector>
auto main() -> int
{
try
{
auto hasTextures = int{0};
hip_check(hipDeviceGetAttribute(&hasTextures, hipDeviceAttributeImageSupport, 0));
// Fetch device properties
auto devProps = hipDeviceProp_t{};
hip_check(hipGetDeviceProperties(&devProps, 0));
auto const numStreams = devProps.asyncEngineCount;
std::cout << "Device has " << numStreams << " asynchronous engines; preprocessing will use "
<< numStreams << " parallel streams." << std::endl;
auto streams = std::vector<hipStream_t>{};
streams.resize(numStreams);
for(auto&& stream : streams)
hip_check(hipStreamCreate(&stream));
auto r = static_cast<float*>(nullptr);
auto R = static_cast<hipfftComplex*>(nullptr);
auto forwardPlans = std::vector<hipfftHandle>{};
auto forwardSizes = std::vector<std::size_t>{};
auto backwardPlans = std::vector<hipfftHandle>{};
auto backwardSizes = std::vector<std::size_t>{};
forwardPlans.resize(numStreams);
forwardSizes.resize(numStreams);
backwardPlans.resize(numStreams);
backwardSizes.resize(numStreams);
auto projections = std::vector<float*>{};
auto projectionPitches = std::vector<std::size_t>{};
auto expandedProjections = std::vector<float*>{};
auto expandedPitches = std::vector<std::size_t>{};
auto transformedProjections = std::vector<hipfftComplex*>{};
auto transformedPitches = std::vector<std::size_t>{};
auto textureProjections = std::vector<hipTextureObject_t>{};
auto projGeom = phantom::make_projectionGeometry();
auto volGeom = phantom::make_volumeGeometry();
auto phantomProjections = phantom::make_projections(projGeom, volGeom, streams);
std::cout << "Initializing... " << std::flush;
auto stream = streams.at(0);
// Create filter kernel
hip_check(hipMalloc(reinterpret_cast<void**>(&r), projGeom.dimFFT.x * sizeof(float)));
auto const creationBlocks = std::max((projGeom.dimFFT.x / 1024u), 1u);
filter_creation_kernel<<<creationBlocks, 1024, 0, stream>>>(r, projGeom.s_dimFFT.x, projGeom.pixelDim.x);
hip_check(hipMalloc(reinterpret_cast<void**>(&R), projGeom.dimTrans.x * sizeof(hipfftComplex)));
auto filterPlan = hipfftHandle{};
hipfft_check(hipfftPlan1d(&filterPlan, projGeom.dimFFT.x, HIPFFT_R2C, 1));
hipfft_check(hipfftSetStream(filterPlan, stream));
hipfft_check(hipfftExecR2C(filterPlan, r, R));
auto absoluteBlocks = (projGeom.dimTrans.x / 1024u) + 1u;
filter_absolute_kernel<<<absoluteBlocks, 1024, 0, stream>>>(R, projGeom.dimTrans.x, projGeom.pixelDim.x);
hip_check(hipStreamSynchronize(stream));
hipfft_check(hipfftDestroy(filterPlan));
hip_check(hipFree(r));
auto const inputProjSingle = projGeom.dim.x * projGeom.dim.y * sizeof(std::uint16_t);
auto const inputProjTotal = inputProjSingle * numStreams;
auto const projSingle = projGeom.dim.x * projGeom.dim.y * sizeof(float);
auto const projTotal = projSingle * numStreams;
auto const expandedSingle = projGeom.dimFFT.x * projGeom.dimFFT.y * sizeof(float);
auto const expandedTotal = expandedSingle * numStreams;
auto const transformedSingle = projGeom.dimTrans.x * projGeom.dimTrans.y * sizeof(hipfftComplex);
auto const transformedTotal = transformedSingle * numStreams;
auto const volumeTotal = volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float);
auto const memTotal = inputProjTotal + projTotal + expandedTotal + transformedTotal + volumeTotal;
auto devMemFree = std::size_t{};
auto devMemTotal = std::size_t{};
hip_check(hipMemGetInfo(&devMemFree, &devMemTotal));
auto memRequired = static_cast<std::size_t>(memTotal);
if(memRequired > devMemFree)
{
std::cerr << "Not enough device memory. Required: " << memRequired
<< ", available: " << devMemFree << std::endl;
return EXIT_FAILURE;
}
std::cout << "Done!" << std::endl;
std::cout << "Volume dimensions: " << volGeom.dim.x << " x "
<< volGeom.dim.y << " x "
<< volGeom.dim.z << std::endl;
// Initialize per-stream data
for(auto streamIdx = 0u; streamIdx < streams.size(); ++streamIdx)
{
std::cout << "Initializing stream " << streamIdx << "... " << std::flush;
auto stream = streams.at(streamIdx);
auto proj = static_cast<float*>(nullptr);
auto projPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&proj), &projPitch, projGeom.dim.x * sizeof(float), projGeom.dim.y
));
projections.push_back(proj);
projectionPitches.push_back(projPitch);
auto expanded = static_cast<float*>(nullptr);
auto expandedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&expanded),
&expandedPitch,
projGeom.dimFFT.x * sizeof(float),
projGeom.dimFFT.y
));
expandedProjections.push_back(expanded);
expandedPitches.push_back(expandedPitch);
auto transformed = static_cast<hipfftComplex*>(nullptr);
auto transformedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&transformed),
&transformedPitch,
projGeom.dimTrans.x * sizeof(hipfftComplex),
projGeom.dimTrans.y
));
transformedProjections.push_back(transformed);
transformedPitches.push_back(transformedPitch);
auto& forward = forwardPlans.at(streamIdx);
auto& forwardSize = forwardSizes.at(streamIdx);
auto fw_inembed = static_cast<int>(expandedPitch / sizeof(float));
auto fw_istride = 1;
auto fw_idist = fw_inembed;
auto fw_onembed = static_cast<int>(transformedPitch / sizeof(hipfftComplex));
auto fw_ostride = 1;
auto fw_odist = fw_onembed;
hipfft_check(hipfftCreate(&forward));
hipfft_check(hipfftMakePlanMany(forward, 1, &projGeom.s_dimFFT.x,
&fw_inembed, 1, fw_idist,
&fw_onembed, 1, fw_odist,
HIPFFT_R2C, projGeom.s_dimFFT.y, &forwardSize));
hipfft_check(hipfftSetStream(forward, stream));
auto& backward = backwardPlans.at(streamIdx);
auto& backwardSize = backwardSizes.at(streamIdx);
auto bw_inembed = fw_onembed;
auto bw_istride = fw_ostride;
auto bw_idist = fw_odist;
auto bw_onembed = fw_inembed;
auto bw_ostride = fw_istride;
auto bw_odist = fw_idist;
hipfft_check(hipfftCreate(&backward));
hipfft_check(hipfftMakePlanMany(backward, 1, &projGeom.s_dimFFT.x,
&bw_inembed, bw_istride, bw_idist,
&bw_onembed, bw_ostride, bw_odist,
HIPFFT_C2R, projGeom.s_dimFFT.y, &backwardSize));
hipfft_check(hipfftSetStream(backward, stream));
if(hasTextures)
{
// create a HIP texture from the projection
auto resDesc = hipResourceDesc{};
resDesc.resType = hipResourceTypePitch2D;
resDesc.res.pitch2D.desc = hipCreateChannelDesc<float>();
resDesc.res.pitch2D.devPtr = static_cast<void*>(proj);
resDesc.res.pitch2D.width = projGeom.dim.x;
resDesc.res.pitch2D.height = projGeom.dim.y;
resDesc.res.pitch2D.pitchInBytes = projPitch;
auto texDesc = hipTextureDesc{};
texDesc.addressMode[0] = hipAddressModeBorder;
texDesc.addressMode[1] = hipAddressModeBorder;
texDesc.readMode = hipReadModeElementType;
texDesc.borderColor[0] = 0.f;
texDesc.borderColor[0] = 0.f;
texDesc.filterMode = hipFilterModeLinear;
texDesc.normalizedCoords = 0;
auto& projTex = textureProjections.emplace_back();
hip_check(hipCreateTextureObject(&projTex, &resDesc, &texDesc, nullptr));
}
std::cout << "Done!" << std::endl;
}
create_volume("volume.tif");
auto hostVolPtr = static_cast<float*>(nullptr);
hip_check(hipHostMalloc(
reinterpret_cast<void**>(&hostVolPtr),
volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float),
hipHostMallocDefault
));
auto hostVol = make_hipPitchedPtr(
hostVolPtr, volGeom.dim.x * sizeof(float), volGeom.dim.x, volGeom.dim.y
);
auto vol = hipPitchedPtr{};
auto volExt = make_hipExtent(volGeom.dim.x * sizeof(float), volGeom.dim.y, volGeom.dim.z);
hip_check(hipMalloc3D(&vol, volExt));
hip_check(hipMemset3D(vol, 0, volExt));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN LOOP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// [sphinx-graph-vars-start]
auto graphCreated = false;
auto graphExec = hipGraphExec_t{};
auto graphFinalCreated = false;
auto graphExecFinal = hipGraphExec_t{};
auto graphStream = hipStream_t{};
hip_check(hipStreamCreate(&graphStream));
// [sphinx-graph-vars-end]
auto start = std::chrono::steady_clock::now();
auto projIdx = 0u;
while(projIdx < projGeom.numProj)
{
// [sphinx-begin-capture-start]
// Capture the current batch into a graph template
auto graph = hipGraph_t{};
hip_check(hipStreamBeginCapture(streams.at(0), hipStreamCaptureModeGlobal));
// [sphinx-begin-capture-end]
auto batchSize = std::min(numStreams, static_cast<int>(projGeom.numProj - projIdx));
// [sphinx-fork-start]
// Fork: Record events on stream 0, then have other streams wait
for(auto streamIdx = 1; streamIdx < batchSize; ++streamIdx)
{
auto forkEvent = hipEvent_t{};
hip_check(hipEventCreate(&forkEvent));
hip_check(hipEventRecord(forkEvent, streams.at(0)));
hip_check(hipStreamWaitEvent(streams.at(streamIdx), forkEvent, 0));
hip_check(hipEventDestroy(forkEvent)); // Can destroy after wait is recorded
}
// [sphinx-fork-end]
// Launch batch in parallel streams
for(auto streamIdx = 0; streamIdx < batchSize; ++streamIdx, ++projIdx)
{
auto stream = streams.at(streamIdx);
auto threadsPerBlock = dim3{32, 32, 1};
auto blocksPerGrid = dim3{
(projGeom.dim.x / threadsPerBlock.x) + 1, (projGeom.dim.y / threadsPerBlock.y) + 1, 1
};
auto inputPitchedPtr = phantomProjections.at(projIdx);
auto input = static_cast<std::uint16_t*>(inputPitchedPtr.ptr);
auto inputPitch = inputPitchedPtr.pitch;
auto proj = projections.at(streamIdx);
auto projPitch = projectionPitches.at(streamIdx);
normalization_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
input, inputPitch, proj, projPitch, projGeom.dim, projGeom.bps
);
log_transformation_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(proj, projPitch, projGeom.dim);
weighting_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
proj,
projPitch,
projGeom.dim,
projGeom.d_sd,
projGeom.d_so,
projGeom.minCoord,
projGeom.pixelDim
);
// Expand projection to filter length
auto expanded = expandedProjections.at(streamIdx);
auto expandedPitch = expandedPitches.at(streamIdx);
hip_check(hipMemset2DAsync(
expanded, expandedPitch, 0, projGeom.dimFFT.x * sizeof(float), projGeom.dimFFT.y, stream
));
hip_check(hipMemcpy2DAsync(
expanded,
expandedPitch,
proj,
projPitch,
projGeom.dim.x * sizeof(float),
projGeom.dim.y,
hipMemcpyDeviceToDevice,
stream
));
// R2C Fourier-transform projection
auto transformed = transformedProjections.at(streamIdx);
auto transformedPitch = transformedPitches.at(streamIdx);
hip_check(hipMemset2DAsync(
transformed,
transformedPitch,
0,
projGeom.dimTrans.x * sizeof(hipfftComplex),
projGeom.dimTrans.y,
stream
));
auto& forward = forwardPlans.at(streamIdx);
hipfft_check(hipfftExecR2C(forward, expanded, transformed));
// Apply filter
auto filterBlocksPerGrid = dim3{
(projGeom.dimTrans.x / threadsPerBlock.x) + 1,
(projGeom.dimTrans.y / threadsPerBlock.y) + 1,
1
};
filter_application_kernel<<<filterBlocksPerGrid, threadsPerBlock, 0, stream>>>(
transformed, transformedPitch, R, projGeom.dimTrans
);
auto& backward = backwardPlans.at(streamIdx);
hipfft_check(hipfftExecC2R(backward, transformed, expanded));
// Shrink projection to original size and normalize
hip_check(hipMemcpy2DAsync(
proj,
projPitch,
expanded,
expandedPitch,
projGeom.dim.x * sizeof(float),
projGeom.dim.y,
hipMemcpyDeviceToDevice,
stream
));
filter_normalization_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
proj, projPitch, projGeom.dimFFT.x, projGeom.dim
);
// Backprojection
auto thetaDeg = projGeom.thetaSign * projGeom.thetaStep * projIdx; // Current angle
auto thetaRad = thetaDeg * std::numbers::pi_v<float> / 180.f; // Convert to radians
auto sinTheta = std::sin(thetaRad);
auto cosTheta = std::cos(thetaRad);
auto bpBlockSize = dim3{32u, 8u, 4u};
auto bpBlocks = dim3{
static_cast<std::uint32_t>(volGeom.dim.x / bpBlockSize.x + 1),
static_cast<std::uint32_t>(volGeom.dim.y / bpBlockSize.y + 1),
static_cast<std::uint32_t>(volGeom.dim.z / bpBlockSize.z + 1)
};
if(hasTextures)
{
auto& projTex = textureProjections.at(streamIdx);
backprojection_kernel<<<bpBlocks, bpBlockSize, 0, stream>>>(
static_cast<float*>(vol.ptr),
vol.pitch,
volGeom.dim,
volGeom.voxelDim,
projTex,
projGeom.minCoord,
sinTheta,
cosTheta,
projGeom.pixelDim,
projGeom.d_sd,
projGeom.d_so
);
}
else
{
// Fallback for devices without support for texture instructions
backprojection_kernel_no_tex<<<bpBlocks, bpBlockSize, 0, stream>>>(
static_cast<float*>(vol.ptr),
vol.pitch,
volGeom.dim,
volGeom.voxelDim,
proj,
projPitch,
projGeom.dim,
projGeom.minCoord,
sinTheta,
cosTheta,
projGeom.pixelDim,
projGeom.d_sd,
projGeom.d_so
);
}
}
// [sphinx-join-start]
// Join: Record events on all streams except stream 0, then have stream 0 wait
for(auto streamIdx = 1; streamIdx < batchSize; ++streamIdx)
{
auto joinEvent = hipEvent_t{};
hip_check(hipEventCreate(&joinEvent));
hip_check(hipEventRecord(joinEvent, streams.at(streamIdx)));
hip_check(hipStreamWaitEvent(streams.at(0), joinEvent, 0));
hip_check(hipEventDestroy(joinEvent)); // Can destroy after wait is recorded
}
// [sphinx-join-end]
// [sphinx-stop-capture-start]
// Stop capturing -- this will stop capturing on all streams
hip_check(hipStreamEndCapture(streams.at(0), &graph));
// [sphinx-stop-capture-end]
// Instantiate and launch the graph
if(batchSize == numStreams)
{
// [sphinx-graph-instantiate-start]
if(!graphCreated)
{
hip_check(hipGraphDebugDotPrint(graph, "graph_capture.dot", hipGraphDebugDotFlagsVerbose));
hip_check(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExec, graphStream));
graphCreated = true;
}
// [sphinx-graph-instantiate-end]
// [sphinx-graph-update-start]
else
{
// Update existing executable graph after each iteration with new input data
auto result = hipGraphExecUpdateResult{};
auto errorNode = hipGraphNode_t{};
hip_check(hipGraphExecUpdate(graphExec, graph, &errorNode, &result));
if(result != hipGraphExecUpdateSuccess)
{
auto msg = std::string{"Failed to update graph: "};
switch(result)
{
case hipGraphExecUpdateError:
msg += "Invalid value.";
break;
case hipGraphExecUpdateErrorFunctionChanged:
msg += "Function of kernel node changed.";
break;
case hipGraphExecUpdateErrorNodeTypeChanged:
msg += "Type of node changed.";
break;
case hipGraphExecUpdateErrorNotSupported:
msg += "Something about the node is not supported.";
break;
case hipGraphExecUpdateErrorParametersChanged:
msg += "Unsupported parameter change.";
break;
case hipGraphExecUpdateErrorTopologyChanged:
msg += "Graph topology changed.";
break;
case hipGraphExecUpdateErrorUnsupportedFunctionChange:
msg += "Unsupported change of kernel node function.";
break;
default:
msg += "Unknown error.";
break;
}
throw std::runtime_error{msg};
}
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExec, graphStream));
}
// [sphinx-graph-update-end]
}
else
{
// [sphinx-graph-final-start]
hip_check(hipGraphDebugDotPrint(graph, "graph_capture_final.dot", hipGraphDebugDotFlagsVerbose));
// Incomplete batch: topology changed, must instantiate new executable graph
hip_check(hipGraphInstantiate(&graphExecFinal, graph, nullptr, nullptr, 0));
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExecFinal, graphStream));
// [sphinx-graph-final-end]
graphFinalCreated = true;
}
}
// Obtain reconstruction time before copying back the result
auto stop = std::chrono::steady_clock::time_point{};
hip_check(hipLaunchHostFunc(graphStream, [](void* data)
{
auto& stop = *(static_cast<std::chrono::steady_clock::time_point*>(data));
stop = std::chrono::steady_clock::now();
}, static_cast<void*>(&stop)));
// Copy volume back to host and save
auto memcpyParams = hipMemcpy3DParms{};
std::memset(&memcpyParams, 0, sizeof(hipMemcpy3DParms));
memcpyParams.dstPos = make_hipPos(0, 0, 0);
memcpyParams.dstPtr = hostVol;
memcpyParams.srcPos = make_hipPos(0, 0, 0);
memcpyParams.srcPtr = vol;
memcpyParams.extent = volExt;
memcpyParams.kind = hipMemcpyDeviceToHost;
hip_check(hipMemcpy3DAsync(&memcpyParams, graphStream));
auto saveVolArgs = new save_volume_args
{
"volume.tif",
hostVolPtr,
volGeom.dim.x, volGeom.dim.y, volGeom.dim.z,
volGeom.voxelDim.x, volGeom.voxelDim.y
};
hip_check(hipLaunchHostFunc(graphStream, save_volume, saveVolArgs));
std::cout << "All work items enqueued, waiting for completion... " << std::flush;
hip_check(hipStreamSynchronize(graphStream));
std::cout << "Done!" << std::endl;
auto const elapsed = std::chrono::duration<double>{stop - start};
std::cout << "Reconstruction time: " << elapsed.count() << 's' << std::endl;
// Cleanup
if(graphFinalCreated)
hip_check(hipGraphExecDestroy(graphExecFinal));
hip_check(hipGraphExecDestroy(graphExec));
hip_check(hipStreamDestroy(graphStream));
hip_check(hipFree(vol.ptr));
hip_check(hipFreeHost(hostVolPtr));
if(hasTextures)
{
for(auto&& tex : textureProjections)
hip_check(hipDestroyTextureObject(tex));
}
for(auto&& plan : backwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& plan : forwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& p : transformedProjections)
hip_check(hipFree(p));
for(auto&& p : expandedProjections)
hip_check(hipFree(p));
for(auto&& p : projections)
hip_check(hipFree(p));
for(auto&& p : phantomProjections)
hip_check(hipFree(p.ptr));
hip_check(hipFree(R));
for(auto&& stream : streams)
hip_check(hipStreamDestroy(stream));
hip_check(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
catch(std::runtime_error const& e)
{
std::cerr << "Caught runtime error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
@@ -0,0 +1,804 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "backprojection.hpp"
#include "filtering.hpp"
#include "log_transform.hpp"
#include "normalization.hpp"
#include "phantom.hpp"
#include "projection.hpp"
#include "utility.hpp"
#include "weighting.hpp"
#include "volume.hpp"
#include <hip/hip_runtime.h>
#include <hipfft/hipfft.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iterator>
#include <numbers>
#include <ostream>
#include <set>
#include <stdexcept>
#include <vector>
auto main() -> int
{
try
{
auto hasTextures = int{0};
hip_check(hipDeviceGetAttribute(&hasTextures, hipDeviceAttributeImageSupport, 0));
// Fetch device properties
auto devProps = hipDeviceProp_t{};
hip_check(hipGetDeviceProperties(&devProps, 0));
auto const numBranches = devProps.asyncEngineCount;
std::cout << "Device supports " << numBranches << " asynchronous engines; preprocessing will use "
<< numBranches << " parallel branches." << std::endl;
// For interoperability with hipFFT we require streams and events
auto streams = std::vector<hipStream_t>{};
streams.resize(numBranches);
for(auto&& stream : streams)
hip_check(hipStreamCreate(&stream));
auto r = static_cast<float*>(nullptr);
auto R = static_cast<hipfftComplex*>(nullptr);
auto forwardPlans = std::vector<hipfftHandle>{};
auto forwardSizes = std::vector<std::size_t>{};
auto backwardPlans = std::vector<hipfftHandle>{};
auto backwardSizes = std::vector<std::size_t>{};
forwardPlans.resize(numBranches);
forwardSizes.resize(numBranches);
backwardPlans.resize(numBranches);
backwardSizes.resize(numBranches);
auto projections = std::vector<float*>{};
auto projectionPitches = std::vector<std::size_t>{};
auto expandedProjections = std::vector<float*>{};
auto expandedPitches = std::vector<std::size_t>{};
auto transformedProjections = std::vector<hipfftComplex*>{};
auto transformedPitches = std::vector<std::size_t>{};
auto textureProjections = std::vector<hipTextureObject_t>{};
auto projGeom = phantom::make_projectionGeometry();
auto volGeom = phantom::make_volumeGeometry();
auto phantomProjections = phantom::make_projections(projGeom, volGeom, streams);
std::cout << "Initializing... " << std::flush;
auto stream = streams.at(0);
// Create filter kernel
hip_check(hipMalloc(reinterpret_cast<void**>(&r), projGeom.dimFFT.x * sizeof(float)));
auto const creationBlocks = std::max((projGeom.dimFFT.x / 1024u), 1u);
filter_creation_kernel<<<creationBlocks, 1024, 0, stream>>>(r, projGeom.s_dimFFT.x, projGeom.pixelDim.x);
hip_check(hipMalloc(reinterpret_cast<void**>(&R), projGeom.dimTrans.x * sizeof(hipfftComplex)));
auto filterPlan = hipfftHandle{};
hipfft_check(hipfftPlan1d(&filterPlan, projGeom.dimFFT.x, HIPFFT_R2C, 1));
hipfft_check(hipfftSetStream(filterPlan, stream));
hipfft_check(hipfftExecR2C(filterPlan, r, R));
auto absoluteBlocks = (projGeom.dimTrans.x / 1024u) + 1u;
filter_absolute_kernel<<<absoluteBlocks, 1024, 0, stream>>>(R, projGeom.dimTrans.x, projGeom.pixelDim.x);
hip_check(hipStreamSynchronize(stream));
hipfft_check(hipfftDestroy(filterPlan));
hip_check(hipFree(r));
auto const inputProjSingle = projGeom.dim.x * projGeom.dim.y * sizeof(std::uint16_t);
auto const inputProjTotal = inputProjSingle * numBranches;
auto const projSingle = projGeom.dim.x * projGeom.dim.y * sizeof(float);
auto const projTotal = projSingle * numBranches;
auto const expandedSingle = projGeom.dimFFT.x * projGeom.dimFFT.y * sizeof(float);
auto const expandedTotal = expandedSingle * numBranches;
auto const transformedSingle = projGeom.dimTrans.x * projGeom.dimTrans.y * sizeof(hipfftComplex);
auto const transformedTotal = transformedSingle * numBranches;
auto const volumeTotal = volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float);
auto const memTotal = inputProjTotal + projTotal + expandedTotal + transformedTotal + volumeTotal;
auto devMemFree = std::size_t{};
auto devMemTotal = std::size_t{};
hip_check(hipMemGetInfo(&devMemFree, &devMemTotal));
auto memRequired = static_cast<std::size_t>(memTotal);
if(memRequired > devMemFree)
{
std::cerr << "Not enough device memory. Required: " << memRequired
<< ", available: " << devMemFree << std::endl;
return EXIT_FAILURE;
}
std::cout << "Done!" << std::endl;
std::cout << "Volume dimensions: " << volGeom.dim.x << " x "
<< volGeom.dim.y << " x "
<< volGeom.dim.z << std::endl;
// Initialize per-branch data
for(auto branchIdx = 0; branchIdx < numBranches; ++branchIdx)
{
std::cout << "Initializing branch #" << branchIdx << "... " << std::flush;
auto stream = streams.at(branchIdx);
auto proj = static_cast<float*>(nullptr);
auto projPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&proj), &projPitch, projGeom.dim.x * sizeof(float), projGeom.dim.y
));
projections.push_back(proj);
projectionPitches.push_back(projPitch);
auto expanded = static_cast<float*>(nullptr);
auto expandedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&expanded),
&expandedPitch,
projGeom.dimFFT.x * sizeof(float),
projGeom.dimFFT.y
));
expandedProjections.push_back(expanded);
expandedPitches.push_back(expandedPitch);
auto transformed = static_cast<hipfftComplex*>(nullptr);
auto transformedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&transformed),
&transformedPitch,
projGeom.dimTrans.x * sizeof(hipfftComplex),
projGeom.dimTrans.y
));
transformedProjections.push_back(transformed);
transformedPitches.push_back(transformedPitch);
auto& forward = forwardPlans.at(branchIdx);
auto& forwardSize = forwardSizes.at(branchIdx);
auto fw_inembed = static_cast<int>(expandedPitch / sizeof(float));
auto fw_istride = 1;
auto fw_idist = fw_inembed;
auto fw_onembed = static_cast<int>(transformedPitch / sizeof(hipfftComplex));
auto fw_ostride = 1;
auto fw_odist = fw_onembed;
hipfft_check(hipfftCreate(&forward));
hipfft_check(hipfftMakePlanMany(forward, 1, &projGeom.s_dimFFT.x,
&fw_inembed, 1, fw_idist,
&fw_onembed, 1, fw_odist,
HIPFFT_R2C, projGeom.s_dimFFT.y, &forwardSize));
hipfft_check(hipfftSetStream(forward, stream));
auto& backward = backwardPlans.at(branchIdx);
auto& backwardSize = backwardSizes.at(branchIdx);
auto bw_inembed = fw_onembed;
auto bw_istride = fw_ostride;
auto bw_idist = fw_odist;
auto bw_onembed = fw_inembed;
auto bw_ostride = fw_istride;
auto bw_odist = fw_idist;
hipfft_check(hipfftCreate(&backward));
hipfft_check(hipfftMakePlanMany(backward, 1, &projGeom.s_dimFFT.x,
&bw_inembed, bw_istride, bw_idist,
&bw_onembed, bw_ostride, bw_odist,
HIPFFT_C2R, projGeom.s_dimFFT.y, &backwardSize));
hipfft_check(hipfftSetStream(backward, stream));
if(hasTextures)
{
// create a HIP texture from the projection
auto resDesc = hipResourceDesc{};
resDesc.resType = hipResourceTypePitch2D;
resDesc.res.pitch2D.desc = hipCreateChannelDesc<float>();
resDesc.res.pitch2D.devPtr = static_cast<void*>(proj);
resDesc.res.pitch2D.width = projGeom.dim.x;
resDesc.res.pitch2D.height = projGeom.dim.y;
resDesc.res.pitch2D.pitchInBytes = projPitch;
auto texDesc = hipTextureDesc{};
texDesc.addressMode[0] = hipAddressModeBorder;
texDesc.addressMode[1] = hipAddressModeBorder;
texDesc.readMode = hipReadModeElementType;
texDesc.borderColor[0] = 0.f;
texDesc.borderColor[0] = 0.f;
texDesc.filterMode = hipFilterModeLinear;
texDesc.normalizedCoords = 0;
auto& projTex = textureProjections.emplace_back();
hip_check(hipCreateTextureObject(&projTex, &resDesc, &texDesc, nullptr));
}
std::cout << "Done!" << std::endl;
}
create_volume("volume.tif");
auto hostVolPtr = static_cast<float*>(nullptr);
hip_check(hipHostMalloc(
reinterpret_cast<void**>(&hostVolPtr),
volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float),
hipHostMallocDefault
));
auto hostVol = make_hipPitchedPtr(
hostVolPtr, volGeom.dim.x * sizeof(float), volGeom.dim.x, volGeom.dim.y
);
auto vol = hipPitchedPtr{};
auto volExt = make_hipExtent(volGeom.dim.x * sizeof(float), volGeom.dim.y, volGeom.dim.z);
hip_check(hipMalloc3D(&vol, volExt));
hip_check(hipMemset3D(vol, 0, volExt));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN LOOP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
auto graphCreated = false;
auto graphExec = hipGraphExec_t{};
auto graphFinalCreated = false;
auto graphExecFinal = hipGraphExec_t{};
auto graphStream = hipStream_t{};
hip_check(hipStreamCreate(&graphStream));
auto start = std::chrono::steady_clock::now();
auto projIdx = 0u;
while(projIdx < projGeom.numProj)
{
auto batchSize = std::min(numBranches, static_cast<int>(projGeom.numProj - projIdx));
// Create graph for current batch
auto graph = hipGraph_t{};
hip_check(hipGraphCreate(&graph, 0));
// Add nodes for each projection in batch
for(auto branchIdx = 0; branchIdx < batchSize; ++branchIdx, ++projIdx)
{
auto stream = streams.at(branchIdx);
auto threadsPerBlock = dim3{32, 32, 1};
auto blocksPerGrid = dim3{
(projGeom.dim.x / threadsPerBlock.x) + 1, (projGeom.dim.y / threadsPerBlock.y) + 1, 1
};
auto inputPitchedPtr = phantomProjections.at(projIdx);
auto input = static_cast<std::uint16_t*>(inputPitchedPtr.ptr);
auto inputPitch = inputPitchedPtr.pitch;
auto proj = projections.at(branchIdx);
auto projPitch = projectionPitches.at(branchIdx);
void* normalizationKernelParams[] =
{
static_cast<void*>(&input),
static_cast<void*>(&inputPitch),
static_cast<void*>(&proj),
static_cast<void*>(&projPitch),
static_cast<void*>(&projGeom.dim),
static_cast<void*>(&projGeom.bps)
};
auto normalizationKernelNodeParams = hipKernelNodeParams{};
normalizationKernelNodeParams.blockDim = threadsPerBlock;
normalizationKernelNodeParams.extra = nullptr;
normalizationKernelNodeParams.func = reinterpret_cast<void*>(normalization_kernel);
normalizationKernelNodeParams.gridDim = blocksPerGrid;
normalizationKernelNodeParams.kernelParams = normalizationKernelParams;
normalizationKernelNodeParams.sharedMemBytes = 0;
auto normalizationKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&normalizationKernelNode, graph, nullptr, 0, &normalizationKernelNodeParams
));
void* logTransformationKernelParams[] =
{
static_cast<void*>(&proj),
static_cast<void*>(&projPitch),
static_cast<void*>(&projGeom.dim)
};
auto logTransformationKernelNodeParams = hipKernelNodeParams{};
logTransformationKernelNodeParams.blockDim = threadsPerBlock;
logTransformationKernelNodeParams.extra = nullptr;
logTransformationKernelNodeParams.func = reinterpret_cast<void*>(log_transformation_kernel);
logTransformationKernelNodeParams.gridDim = blocksPerGrid;
logTransformationKernelNodeParams.kernelParams = logTransformationKernelParams;
logTransformationKernelNodeParams.sharedMemBytes = 0;
auto logTransformationKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&logTransformationKernelNode,
graph,
&normalizationKernelNode,
1,
&logTransformationKernelNodeParams
));
// [sphinx-weighting-node-start]
void* weightingKernelParams[] =
{
static_cast<void*>(&proj),
static_cast<void*>(&projPitch),
static_cast<void*>(&projGeom.dim),
static_cast<void*>(&projGeom.d_sd),
static_cast<void*>(&projGeom.d_so),
static_cast<void*>(&projGeom.minCoord),
static_cast<void*>(&projGeom.pixelDim)
};
auto weightingKernelNodeParams = hipKernelNodeParams{};
weightingKernelNodeParams.blockDim = threadsPerBlock;
weightingKernelNodeParams.extra = nullptr;
weightingKernelNodeParams.func = reinterpret_cast<void*>(weighting_kernel);
weightingKernelNodeParams.gridDim = blocksPerGrid;
weightingKernelNodeParams.kernelParams = weightingKernelParams;
weightingKernelNodeParams.sharedMemBytes = 0;
auto weightingKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&weightingKernelNode, graph, &logTransformationKernelNode, 1, &weightingKernelNodeParams
));
// [sphinx-weighting-node-end]
// Expand projection to filter length
auto expanded = expandedProjections.at(branchIdx);
auto expandedPitch = expandedPitches.at(branchIdx);
// [sphinx-memset-node-start]
auto expandedMemsetNodeParams = hipMemsetParams{};
expandedMemsetNodeParams.dst = static_cast<void*>(expanded);
expandedMemsetNodeParams.elementSize = sizeof(float);
expandedMemsetNodeParams.height = projGeom.dimFFT.y;
expandedMemsetNodeParams.pitch = expandedPitch;
expandedMemsetNodeParams.value = 0;
expandedMemsetNodeParams.width = projGeom.dimFFT.x;
auto expandedMemsetNode = hipGraphNode_t{};
hip_check(hipGraphAddMemsetNode(
&expandedMemsetNode, graph, &weightingKernelNode, 1, &expandedMemsetNodeParams
));
// [sphinx-memset-node-end]
auto copyProjToExpandedNodeParams = hipMemcpy3DParms{};
std::memset(&copyProjToExpandedNodeParams, 0, sizeof(hipMemcpy3DParms));
copyProjToExpandedNodeParams.srcPos = make_hipPos(0, 0, 0);
copyProjToExpandedNodeParams.srcPtr = make_hipPitchedPtr(
static_cast<void*>(proj), projPitch, projGeom.dim.x, projGeom.dim.y);
copyProjToExpandedNodeParams.dstPos = make_hipPos(0, 0, 0);
copyProjToExpandedNodeParams.dstPtr = make_hipPitchedPtr(
static_cast<void*>(expanded), expandedPitch, projGeom.dimFFT.x, projGeom.dimFFT.y);
copyProjToExpandedNodeParams.extent = make_hipExtent(
projGeom.dim.x * sizeof(float), projGeom.dim.y, 1);
copyProjToExpandedNodeParams.kind = hipMemcpyDeviceToDevice;
auto copyProjToExpandedNode = hipGraphNode_t{};
hip_check(hipGraphAddMemcpyNode(
&copyProjToExpandedNode,
graph,
&expandedMemsetNode,
1,
&copyProjToExpandedNodeParams
));
// R2C Fourier-transform projection
auto transformed = transformedProjections.at(branchIdx);
auto transformedPitch = transformedPitches.at(branchIdx);
auto transformedMemsetNodeParams = hipMemsetParams{};
transformedMemsetNodeParams.dst = static_cast<void*>(transformed);
transformedMemsetNodeParams.elementSize = sizeof(float); // elementSize maximum is 4 bytes
transformedMemsetNodeParams.height = projGeom.dimTrans.y;
transformedMemsetNodeParams.pitch = transformedPitch;
transformedMemsetNodeParams.value = 0;
transformedMemsetNodeParams.width = projGeom.dimTrans.x * 2; // hipfftComplex = 2 floats
auto transformedMemsetNode = hipGraphNode_t{};
hip_check(hipGraphAddMemsetNode(
&transformedMemsetNode, graph, &copyProjToExpandedNode, 1, &transformedMemsetNodeParams
));
// [sphinx-before-forward-start]
// Before capturing the FFT operations, obtain the set of nodes already present in the graph
auto nodesBeforeForward = std::vector<hipGraphNode_t>{};
auto numNodesBeforeForward = std::size_t{};
hip_check(hipGraphGetNodes(graph, nullptr, &numNodesBeforeForward));
nodesBeforeForward.resize(numNodesBeforeForward);
hip_check(hipGraphGetNodes(graph, nodesBeforeForward.data(), &numNodesBeforeForward));
auto nodesBeforeForwardSorted = std::set<hipGraphNode_t>{
std::begin(nodesBeforeForward), std::end(nodesBeforeForward)
};
// [sphinx-before-forward-end]
// [sphinx-hipfft-start]
hip_check(hipStreamBeginCaptureToGraph(
stream, graph, &transformedMemsetNode, nullptr, 1, hipStreamCaptureModeGlobal));
auto& forward = forwardPlans.at(branchIdx);
hipfft_check(hipfftExecR2C(forward, expanded, transformed));
hip_check(hipStreamEndCapture(stream, &graph));
// [sphinx-hipfft-end]
// [sphinx-is-leaf-start]
auto is_leaf = [](hipGraphNode_t node)
{
auto numDependentNodes = std::size_t{};
hip_check(hipGraphNodeGetDependentNodes(node, nullptr, &numDependentNodes));
return numDependentNodes == 0;
};
// [sphinx-is-leaf-end]
// [sphinx-after-forward-start]
// Obtain nodes in graph again, the new nodes will be our dependencies for the next step
auto nodesAfterForward = std::vector<hipGraphNode_t>{};
auto numNodesAfterForward = std::size_t{};
hip_check(hipGraphGetNodes(graph, nullptr, &numNodesAfterForward));
nodesAfterForward.resize(numNodesAfterForward);
hip_check(hipGraphGetNodes(graph, nodesAfterForward.data(), &numNodesAfterForward));
auto nodesAfterForwardSorted = std::set<hipGraphNode_t>{
std::begin(nodesAfterForward), std::end(nodesAfterForward)
};
// [sphinx-after-forward-end]
// [sphinx-node-difference-start]
// Compute difference between both sets
auto forwardFFTNodes = std::vector<hipGraphNode_t>{};
std::set_difference(std::begin(nodesAfterForwardSorted), std::end(nodesAfterForwardSorted),
std::begin(nodesBeforeForwardSorted), std::end(nodesBeforeForwardSorted),
std::back_inserter(forwardFFTNodes));
// [sphinx-node-difference-end]
// [sphinx-find-leaf-start]
// Find leaf node in difference set
auto forwardLeafNode = *(std::find_if(std::begin(forwardFFTNodes), std::end(forwardFFTNodes), is_leaf));
// [sphinx-find-leaf-end]
// Apply filter
auto filterBlocksPerGrid = dim3{
(projGeom.dimTrans.x / threadsPerBlock.x) + 1,
(projGeom.dimTrans.y / threadsPerBlock.y) + 1,
1
};
void* filterApplicationKernelParams[] =
{
static_cast<void*>(&transformed),
static_cast<void*>(&transformedPitch),
static_cast<void*>(&R),
static_cast<void*>(&projGeom.dimTrans)
};
auto filterApplicationKernelNodeParams = hipKernelNodeParams{};
filterApplicationKernelNodeParams.blockDim = threadsPerBlock;
filterApplicationKernelNodeParams.extra = nullptr;
filterApplicationKernelNodeParams.func = reinterpret_cast<void*>(filter_application_kernel);
filterApplicationKernelNodeParams.gridDim = filterBlocksPerGrid;
filterApplicationKernelNodeParams.kernelParams = filterApplicationKernelParams;
filterApplicationKernelNodeParams.sharedMemBytes = 0;
auto filterApplicationKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&filterApplicationKernelNode, graph, &forwardLeafNode, 1, &filterApplicationKernelNodeParams
));
// C2R Fourier-transform projection - same node counting procedure as above
auto nodesBeforeBackward = std::vector<hipGraphNode_t>{};
auto numNodesBeforeBackward = std::size_t{};
hip_check(hipGraphGetNodes(graph, nullptr, &numNodesBeforeBackward));
nodesBeforeBackward.resize(numNodesBeforeBackward);
hip_check(hipGraphGetNodes(graph, nodesBeforeBackward.data(), &numNodesBeforeBackward));
auto nodesBeforeBackwardSorted = std::set<hipGraphNode_t>{
std::begin(nodesBeforeBackward), std::end(nodesBeforeBackward)
};
hip_check(hipStreamBeginCaptureToGraph(
stream, graph, &filterApplicationKernelNode, nullptr, 1, hipStreamCaptureModeGlobal
));
auto& backward = backwardPlans.at(branchIdx);
hipfft_check(hipfftExecC2R(backward, transformed, expanded));
hip_check(hipStreamEndCapture(stream, &graph));
auto nodesAfterBackward = std::vector<hipGraphNode_t>{};
auto numNodesAfterBackward = std::size_t{};
hip_check(hipGraphGetNodes(graph, nullptr, &numNodesAfterBackward));
nodesAfterBackward.resize(numNodesAfterBackward);
hip_check(hipGraphGetNodes(graph, nodesAfterBackward.data(), &numNodesAfterBackward));
auto nodesAfterBackwardSorted = std::set<hipGraphNode_t>{
std::begin(nodesAfterBackward), std::end(nodesAfterBackward)
};
auto backwardFFTNodes = std::vector<hipGraphNode_t>{};
std::set_difference(std::begin(nodesAfterBackwardSorted), std::end(nodesAfterBackwardSorted),
std::begin(nodesBeforeBackwardSorted), std::end(nodesBeforeBackwardSorted),
std::back_inserter(backwardFFTNodes));
auto backwardLeafNode = *(
std::find_if(std::begin(backwardFFTNodes), std::end(backwardFFTNodes), is_leaf
));
// Shrink projection to original size and normalize
auto copyExpandedToProjNodeParams = hipMemcpy3DParms{};
std::memset(&copyExpandedToProjNodeParams, 0, sizeof(hipMemcpy3DParms));
copyExpandedToProjNodeParams.srcPos = make_hipPos(0, 0, 0);
copyExpandedToProjNodeParams.srcPtr = make_hipPitchedPtr(
static_cast<void*>(expanded), expandedPitch, projGeom.dimFFT.x, projGeom.dimFFT.y
);
copyExpandedToProjNodeParams.dstPos = make_hipPos(0, 0, 0);
copyExpandedToProjNodeParams.dstPtr = make_hipPitchedPtr(
static_cast<void*>(proj), projPitch, projGeom.dim.x, projGeom.dim.y
);
copyExpandedToProjNodeParams.extent = make_hipExtent(
projGeom.dim.x * sizeof(float), projGeom.dim.y, 1);
copyExpandedToProjNodeParams.kind = hipMemcpyDeviceToDevice;
auto copyExpandedToProjNode = hipGraphNode_t{};
hip_check(hipGraphAddMemcpyNode(
&copyExpandedToProjNode, graph, &backwardLeafNode, 1, &copyExpandedToProjNodeParams
));
void* filterNormalizationKernelParams[] =
{
static_cast<void*>(&proj),
static_cast<void*>(&projPitch),
static_cast<void*>(&projGeom.dimFFT.x),
static_cast<void*>(&projGeom.dim)
};
auto filterNormalizationKernelNodeParams = hipKernelNodeParams{};
filterNormalizationKernelNodeParams.blockDim = threadsPerBlock;
filterNormalizationKernelNodeParams.extra = nullptr;
filterNormalizationKernelNodeParams.func = reinterpret_cast<void*>(filter_normalization_kernel);
filterNormalizationKernelNodeParams.gridDim = blocksPerGrid;
filterNormalizationKernelNodeParams.kernelParams = filterNormalizationKernelParams;
filterNormalizationKernelNodeParams.sharedMemBytes = 0;
auto filterNormalizationKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&filterNormalizationKernelNode,
graph,
&copyExpandedToProjNode,
1,
&filterNormalizationKernelNodeParams));
// Backprojection
auto thetaDeg = projGeom.thetaSign * projGeom.thetaStep * projIdx; // Current angle
auto thetaRad = thetaDeg * std::numbers::pi_v<float> / 180.f; // Convert to radians
auto sinTheta = std::sin(thetaRad);
auto cosTheta = std::cos(thetaRad);
auto bpBlockSize = dim3{32u, 8u, 4u};
auto bpBlocks = dim3{
static_cast<std::uint32_t>(volGeom.dim.x / bpBlockSize.x + 1),
static_cast<std::uint32_t>(volGeom.dim.y / bpBlockSize.y + 1),
static_cast<std::uint32_t>(volGeom.dim.z / bpBlockSize.z + 1)
};
if(hasTextures)
{
auto& projTex = textureProjections.at(branchIdx);
void* backprojectionKernelParams[] =
{
&vol.ptr,
static_cast<void*>(&vol.pitch),
static_cast<void*>(&volGeom.dim),
static_cast<void*>(&volGeom.voxelDim),
static_cast<void*>(&projTex),
static_cast<void*>(&projGeom.minCoord),
static_cast<void*>(&sinTheta),
static_cast<void*>(&cosTheta),
static_cast<void*>(&projGeom.pixelDim),
static_cast<void*>(&projGeom.d_sd),
static_cast<void*>(&projGeom.d_so)
};
auto backprojectionKernelNodeParams = hipKernelNodeParams{};
backprojectionKernelNodeParams.blockDim = bpBlockSize;
backprojectionKernelNodeParams.extra = nullptr;
backprojectionKernelNodeParams.func = reinterpret_cast<void*>(backprojection_kernel);
backprojectionKernelNodeParams.gridDim = bpBlocks;
backprojectionKernelNodeParams.kernelParams = backprojectionKernelParams;
backprojectionKernelNodeParams.sharedMemBytes = 0;
auto backprojectionKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&backprojectionKernelNode,
graph,
&filterNormalizationKernelNode,
1,
&backprojectionKernelNodeParams
));
}
else
{
// Fallback for devices without support for texture instructions
void* backprojectionKernelParams[] =
{
&vol.ptr,
static_cast<void*>(&vol.pitch),
static_cast<void*>(&volGeom.dim),
static_cast<void*>(&volGeom.voxelDim),
static_cast<void*>(&proj),
static_cast<void*>(&projPitch),
static_cast<void*>(&projGeom.dim),
static_cast<void*>(&projGeom.minCoord),
static_cast<void*>(&sinTheta),
static_cast<void*>(&cosTheta),
static_cast<void*>(&projGeom.pixelDim),
static_cast<void*>(&projGeom.d_sd),
static_cast<void*>(&projGeom.d_so)
};
auto backprojectionKernelNodeParams = hipKernelNodeParams{};
backprojectionKernelNodeParams.blockDim = bpBlockSize;
backprojectionKernelNodeParams.extra = nullptr;
backprojectionKernelNodeParams.func = reinterpret_cast<void*>(backprojection_kernel_no_tex);
backprojectionKernelNodeParams.gridDim = bpBlocks;
backprojectionKernelNodeParams.kernelParams = backprojectionKernelParams;
backprojectionKernelNodeParams.sharedMemBytes = 0;
auto backprojectionKernelNode = hipGraphNode_t{};
hip_check(hipGraphAddKernelNode(
&backprojectionKernelNode,
graph,
&filterNormalizationKernelNode,
1,
&backprojectionKernelNodeParams
));
}
}
// Instantiate and launch the graph
if(batchSize == numBranches)
{
if(!graphCreated)
{
hip_check(hipGraphDebugDotPrint(graph, "graph_creation.dot", hipGraphDebugDotFlagsVerbose));
hip_check(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExec, graphStream));
graphCreated = true;
}
else
{
// Update existing executable graph after each iteration with new input data
auto result = hipGraphExecUpdateResult{};
auto errorNode = hipGraphNode_t{};
hip_check(hipGraphExecUpdate(graphExec, graph, &errorNode, &result));
if(result != hipGraphExecUpdateSuccess)
{
auto msg = std::string{"Failed to update graph: "};
switch(result)
{
case hipGraphExecUpdateError:
msg += "Invalid value.";
break;
case hipGraphExecUpdateErrorFunctionChanged:
msg += "Function of kernel node changed.";
break;
case hipGraphExecUpdateErrorNodeTypeChanged:
msg += "Type of node changed.";
break;
case hipGraphExecUpdateErrorNotSupported:
msg += "Something about the node is not supported.";
break;
case hipGraphExecUpdateErrorParametersChanged:
msg += "Unsupported parameter change.";
break;
case hipGraphExecUpdateErrorTopologyChanged:
msg += "Graph topology changed.";
break;
case hipGraphExecUpdateErrorUnsupportedFunctionChange:
msg += "Unsupported change of kernel node function.";
break;
default:
msg += "Unknown error.";
break;
}
throw std::runtime_error{msg};
}
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExec, graphStream));
}
}
else
{
hip_check(hipGraphDebugDotPrint(graph, "graph_creation_final.dot", hipGraphDebugDotFlagsVerbose));
// Incomplete batch: topology changed, must instantiate new executable graph
hip_check(hipGraphInstantiate(&graphExecFinal, graph, nullptr, nullptr, 0));
hip_check(hipGraphDestroy(graph));
hip_check(hipGraphLaunch(graphExecFinal, graphStream));
graphFinalCreated = true;
}
}
// Obtain reconstruction time before copying back the result
auto stop = std::chrono::steady_clock::time_point{};
hip_check(hipLaunchHostFunc(graphStream, [](void* data)
{
auto& stop = *(static_cast<std::chrono::steady_clock::time_point*>(data));
stop = std::chrono::steady_clock::now();
}, static_cast<void*>(&stop)));
// Copy volume back to host and save
auto memcpyParams = hipMemcpy3DParms{};
std::memset(&memcpyParams, 0, sizeof(hipMemcpy3DParms));
memcpyParams.dstPos = make_hipPos(0, 0, 0);
memcpyParams.dstPtr = hostVol;
memcpyParams.srcPos = make_hipPos(0, 0, 0);
memcpyParams.srcPtr = vol;
memcpyParams.extent = volExt;
memcpyParams.kind = hipMemcpyDeviceToHost;
hip_check(hipMemcpy3DAsync(&memcpyParams, graphStream));
auto saveVolArgs = new save_volume_args
{
"volume.tif",
hostVolPtr,
volGeom.dim.x, volGeom.dim.y, volGeom.dim.z,
volGeom.voxelDim.x, volGeom.voxelDim.y
};
hip_check(hipLaunchHostFunc(graphStream, save_volume, saveVolArgs));
std::cout << "All work items enqueued, waiting for completion... " << std::flush;
hip_check(hipStreamSynchronize(graphStream));
std::cout << "Done!" << std::endl;
auto const elapsed = std::chrono::duration<double>{stop - start};
std::cout << "Reconstruction time: " << elapsed.count() << 's' << std::endl;
// Cleanup
if(graphFinalCreated)
hip_check(hipGraphExecDestroy(graphExecFinal));
hip_check(hipGraphExecDestroy(graphExec));
hip_check(hipStreamDestroy(graphStream));
hip_check(hipFree(vol.ptr));
hip_check(hipFreeHost(hostVolPtr));
if(hasTextures)
{
for(auto&& tex : textureProjections)
hip_check(hipDestroyTextureObject(tex));
}
for(auto&& plan : backwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& plan : forwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& p : transformedProjections)
hip_check(hipFree(p));
for(auto&& p : expandedProjections)
hip_check(hipFree(p));
for(auto&& p : projections)
hip_check(hipFree(p));
for(auto&& p : phantomProjections)
hip_check(hipFree(p.ptr));
hip_check(hipFree(R));
for(auto&& stream : streams)
hip_check(hipStreamDestroy(stream));
hip_check(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
catch(std::runtime_error const& e)
{
std::cerr << "Caught runtime error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
@@ -0,0 +1,521 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "backprojection.hpp"
#include "filtering.hpp"
#include "log_transform.hpp"
#include "normalization.hpp"
#include "phantom.hpp"
#include "projection.hpp"
#include "utility.hpp"
#include "weighting.hpp"
#include "volume.hpp"
#include <hip/hip_runtime.h>
#include <hipfft/hipfft.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <numbers>
#include <ostream>
#include <set>
#include <stdexcept>
#include <vector>
auto main() -> int
{
try
{
auto hasTextures = int{0};
hip_check(hipDeviceGetAttribute(&hasTextures, hipDeviceAttributeImageSupport, 0));
// [sphinx-async-engine-start]
// Fetch device properties
auto devProps = hipDeviceProp_t{};
hip_check(hipGetDeviceProperties(&devProps, 0));
auto const numStreams = devProps.asyncEngineCount;
std::cout << "Device has " << numStreams << " asynchronous engines; preprocessing will use "
<< numStreams << " parallel streams." << std::endl;
auto streams = std::vector<hipStream_t>{};
streams.resize(numStreams);
for(auto&& stream : streams)
hip_check(hipStreamCreate(&stream));
// [sphinx-async-engine-end]
auto r = static_cast<float*>(nullptr);
auto R = static_cast<hipfftComplex*>(nullptr);
auto forwardPlans = std::vector<hipfftHandle>{};
auto forwardSizes = std::vector<std::size_t>{};
auto backwardPlans = std::vector<hipfftHandle>{};
auto backwardSizes = std::vector<std::size_t>{};
forwardPlans.resize(numStreams);
forwardSizes.resize(numStreams);
backwardPlans.resize(numStreams);
backwardSizes.resize(numStreams);
auto projections = std::vector<float*>{};
auto projectionPitches = std::vector<std::size_t>{};
auto expandedProjections = std::vector<float*>{};
auto expandedPitches = std::vector<std::size_t>{};
auto transformedProjections = std::vector<hipfftComplex*>{};
auto transformedPitches = std::vector<std::size_t>{};
auto textureProjections = std::vector<hipTextureObject_t>{};
auto projGeom = phantom::make_projectionGeometry();
auto volGeom = phantom::make_volumeGeometry();
auto phantomProjections = phantom::make_projections(projGeom, volGeom, streams);
std::cout << "Initializing... " << std::flush;
auto stream = streams.at(0);
// Create filter kernel
hip_check(hipMalloc(reinterpret_cast<void**>(&r), projGeom.dimFFT.x * sizeof(float)));
auto const creationBlocks = std::max((projGeom.dimFFT.x / 1024u), 1u);
filter_creation_kernel<<<creationBlocks, 1024, 0, stream>>>(r, projGeom.s_dimFFT.x, projGeom.pixelDim.x);
hip_check(hipMalloc(reinterpret_cast<void**>(&R), projGeom.dimTrans.x * sizeof(hipfftComplex)));
auto filterPlan = hipfftHandle{};
hipfft_check(hipfftPlan1d(&filterPlan, projGeom.dimFFT.x, HIPFFT_R2C, 1));
hipfft_check(hipfftSetStream(filterPlan, stream));
hipfft_check(hipfftExecR2C(filterPlan, r, R));
auto absoluteBlocks = (projGeom.dimTrans.x / 1024u) + 1u;
filter_absolute_kernel<<<absoluteBlocks, 1024, 0, stream>>>(R, projGeom.dimTrans.x, projGeom.pixelDim.x);
hip_check(hipStreamSynchronize(stream));
hipfft_check(hipfftDestroy(filterPlan));
hip_check(hipFree(r));
auto const inputProjSingle = projGeom.dim.x * projGeom.dim.y * sizeof(std::uint16_t);
auto const inputProjTotal = inputProjSingle * numStreams;
auto const projSingle = projGeom.dim.x * projGeom.dim.y * sizeof(float);
auto const projTotal = projSingle * numStreams;
auto const expandedSingle = projGeom.dimFFT.x * projGeom.dimFFT.y * sizeof(float);
auto const expandedTotal = expandedSingle * numStreams;
auto const transformedSingle = projGeom.dimTrans.x * projGeom.dimTrans.y * sizeof(hipfftComplex);
auto const transformedTotal = transformedSingle * numStreams;
auto const volumeTotal = volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float);
auto const memTotal = inputProjTotal + projTotal + expandedTotal + transformedTotal + volumeTotal;
auto devMemFree = std::size_t{};
auto devMemTotal = std::size_t{};
hip_check(hipMemGetInfo(&devMemFree, &devMemTotal));
auto memRequired = static_cast<std::size_t>(memTotal);
if(memRequired > devMemFree)
{
std::cerr << "Not enough device memory. Required: " << memRequired
<< ", available: " << devMemFree << std::endl;
return EXIT_FAILURE;
}
std::cout << "Done!" << std::endl;
std::cout << "Volume dimensions: " << volGeom.dim.x << " x "
<< volGeom.dim.y << " x "
<< volGeom.dim.z << std::endl;
// Initialize per-stream data
for(auto streamIdx = 0u; streamIdx < streams.size(); ++streamIdx)
{
std::cout << "Initializing stream " << streamIdx << "... " << std::flush;
auto stream = streams.at(streamIdx);
auto proj = static_cast<float*>(nullptr);
auto projPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&proj), &projPitch, projGeom.dim.x * sizeof(float), projGeom.dim.y
));
projections.push_back(proj);
projectionPitches.push_back(projPitch);
auto expanded = static_cast<float*>(nullptr);
auto expandedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&expanded),
&expandedPitch,
projGeom.dimFFT.x * sizeof(float),
projGeom.dimFFT.y
));
expandedProjections.push_back(expanded);
expandedPitches.push_back(expandedPitch);
auto transformed = static_cast<hipfftComplex*>(nullptr);
auto transformedPitch = std::size_t{};
hip_check(hipMallocPitch(
reinterpret_cast<void**>(&transformed),
&transformedPitch,
projGeom.dimTrans.x * sizeof(hipfftComplex),
projGeom.dimTrans.y
));
transformedProjections.push_back(transformed);
transformedPitches.push_back(transformedPitch);
auto& forward = forwardPlans.at(streamIdx);
auto& forwardSize = forwardSizes.at(streamIdx);
auto fw_inembed = static_cast<int>(expandedPitch / sizeof(float));
auto fw_istride = 1;
auto fw_idist = fw_inembed;
auto fw_onembed = static_cast<int>(transformedPitch / sizeof(hipfftComplex));
auto fw_ostride = 1;
auto fw_odist = fw_onembed;
hipfft_check(hipfftCreate(&forward));
hipfft_check(hipfftMakePlanMany(forward, 1, &projGeom.s_dimFFT.x,
&fw_inembed, 1, fw_idist,
&fw_onembed, 1, fw_odist,
HIPFFT_R2C, projGeom.s_dimFFT.y, &forwardSize));
hipfft_check(hipfftSetStream(forward, stream));
auto& backward = backwardPlans.at(streamIdx);
auto& backwardSize = backwardSizes.at(streamIdx);
auto bw_inembed = fw_onembed;
auto bw_istride = fw_ostride;
auto bw_idist = fw_odist;
auto bw_onembed = fw_inembed;
auto bw_ostride = fw_istride;
auto bw_odist = fw_idist;
hipfft_check(hipfftCreate(&backward));
hipfft_check(hipfftMakePlanMany(backward, 1, &projGeom.s_dimFFT.x,
&bw_inembed, bw_istride, bw_idist,
&bw_onembed, bw_ostride, bw_odist,
HIPFFT_C2R, projGeom.s_dimFFT.y, &backwardSize));
hipfft_check(hipfftSetStream(backward, stream));
if(hasTextures)
{
// create a HIP texture from the projection
auto resDesc = hipResourceDesc{};
resDesc.resType = hipResourceTypePitch2D;
resDesc.res.pitch2D.desc = hipCreateChannelDesc<float>();
resDesc.res.pitch2D.devPtr = static_cast<void*>(proj);
resDesc.res.pitch2D.width = projGeom.dim.x;
resDesc.res.pitch2D.height = projGeom.dim.y;
resDesc.res.pitch2D.pitchInBytes = projPitch;
auto texDesc = hipTextureDesc{};
texDesc.addressMode[0] = hipAddressModeBorder;
texDesc.addressMode[1] = hipAddressModeBorder;
texDesc.readMode = hipReadModeElementType;
texDesc.borderColor[0] = 0.f;
texDesc.borderColor[0] = 0.f;
texDesc.filterMode = hipFilterModeLinear;
texDesc.normalizedCoords = 0;
auto& projTex = textureProjections.emplace_back();
hip_check(hipCreateTextureObject(&projTex, &resDesc, &texDesc, nullptr));
}
std::cout << "Done!" << std::endl;
}
create_volume("volume.tif");
auto hostVolPtr = static_cast<float*>(nullptr);
hip_check(hipHostMalloc(
reinterpret_cast<void**>(&hostVolPtr),
volGeom.dim.x * volGeom.dim.y * volGeom.dim.z * sizeof(float),
hipHostMallocDefault
));
auto hostVol = make_hipPitchedPtr(
hostVolPtr, volGeom.dim.x * sizeof(float), volGeom.dim.x, volGeom.dim.y
);
auto vol = hipPitchedPtr{};
auto volExt = make_hipExtent(volGeom.dim.x * sizeof(float), volGeom.dim.y, volGeom.dim.z);
hip_check(hipMalloc3D(&vol, volExt));
hip_check(hipMemset3D(vol, 0, volExt));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN LOOP
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
auto start = std::chrono::steady_clock::now();
// [sphinx-batch-start]
auto projIdx = 0u;
while(projIdx < projGeom.numProj)
{
auto batchSize = std::min(numStreams, static_cast<int>(projGeom.numProj - projIdx));
// Launch batch in parallel streams
for(auto streamIdx = 0; streamIdx < batchSize; ++streamIdx, ++projIdx)
{
auto stream = streams.at(streamIdx);
// [sphinx-batch-end]
auto threadsPerBlock = dim3{32, 32, 1};
auto blocksPerGrid = dim3{
(projGeom.dim.x / threadsPerBlock.x) + 1, (projGeom.dim.y / threadsPerBlock.y) + 1, 1
};
auto inputPitchedPtr = phantomProjections.at(projIdx);
auto input = static_cast<std::uint16_t*>(inputPitchedPtr.ptr);
auto inputPitch = inputPitchedPtr.pitch;
// [sphinx-preprocessing-start]
////////////////////////////////////////////////////////////////////////////////////////////////////
// START HERE
////////////////////////////////////////////////////////////////////////////////////////////////////
auto proj = projections.at(streamIdx);
auto projPitch = projectionPitches.at(streamIdx);
normalization_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
input, inputPitch, proj, projPitch, projGeom.dim, projGeom.bps
);
log_transformation_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(proj, projPitch, projGeom.dim);
weighting_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
proj,
projPitch,
projGeom.dim,
projGeom.d_sd,
projGeom.d_so,
projGeom.minCoord,
projGeom.pixelDim
);
// [sphinx-preprocessing-end]
// [sphinx-proj-to-expanded-start]
// Expand projection to filter length
auto expanded = expandedProjections.at(streamIdx);
auto expandedPitch = expandedPitches.at(streamIdx);
hip_check(hipMemset2DAsync(
expanded, expandedPitch, 0, projGeom.dimFFT.x * sizeof(float), projGeom.dimFFT.y, stream
));
hip_check(hipMemcpy2DAsync(
expanded,
expandedPitch,
proj,
projPitch,
projGeom.dim.x * sizeof(float),
projGeom.dim.y,
hipMemcpyDeviceToDevice,
stream
));
// [sphinx-proj-to-expanded-end]
// [sphinx-forward-start]
// R2C Fourier-transform projection
auto transformed = transformedProjections.at(streamIdx);
auto transformedPitch = transformedPitches.at(streamIdx);
hip_check(hipMemset2DAsync(
transformed,
transformedPitch,
0,
projGeom.dimTrans.x * sizeof(hipfftComplex),
projGeom.dimTrans.y,
stream
));
auto& forward = forwardPlans.at(streamIdx);
hipfft_check(hipfftExecR2C(forward, expanded, transformed));
// [sphinx-forward-end]
// [sphinx-filter-start]
// Apply filter
auto filterBlocksPerGrid = dim3{
(projGeom.dimTrans.x / threadsPerBlock.x) + 1,
(projGeom.dimTrans.y / threadsPerBlock.y) + 1,
1
};
filter_application_kernel<<<filterBlocksPerGrid, threadsPerBlock, 0, stream>>>(
transformed, transformedPitch, R, projGeom.dimTrans
);
auto& backward = backwardPlans.at(streamIdx);
hipfft_check(hipfftExecC2R(backward, transformed, expanded));
// [sphinx-filter-end]
// [sphinx-expanded-to-proj-start]
// Shrink projection to original size and normalize
hip_check(hipMemcpy2DAsync(
proj,
projPitch,
expanded,
expandedPitch,
projGeom.dim.x * sizeof(float),
projGeom.dim.y,
hipMemcpyDeviceToDevice,
stream
));
filter_normalization_kernel<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(
proj, projPitch, projGeom.dimFFT.x, projGeom.dim
);
// [sphinx-expanded-to-proj-end]
// [sphinx-bp-start]
// Backprojection
auto thetaDeg = projGeom.thetaSign * projGeom.thetaStep * projIdx; // Current angle
auto thetaRad = thetaDeg * std::numbers::pi_v<float> / 180.f; // Convert to radians
auto sinTheta = std::sin(thetaRad);
auto cosTheta = std::cos(thetaRad);
auto bpBlockSize = dim3{32u, 8u, 4u};
auto bpBlocks = dim3{
static_cast<std::uint32_t>(volGeom.dim.x / bpBlockSize.x + 1),
static_cast<std::uint32_t>(volGeom.dim.y / bpBlockSize.y + 1),
static_cast<std::uint32_t>(volGeom.dim.z / bpBlockSize.z + 1)
};
if(hasTextures)
{
auto& projTex = textureProjections.at(streamIdx);
backprojection_kernel<<<bpBlocks, bpBlockSize, 0, stream>>>(
static_cast<float*>(vol.ptr),
vol.pitch,
volGeom.dim,
volGeom.voxelDim,
projTex,
projGeom.minCoord,
sinTheta,
cosTheta,
projGeom.pixelDim,
projGeom.d_sd,
projGeom.d_so
);
}
else
{
// Fallback for devices without support for texture instructions
backprojection_kernel_no_tex<<<bpBlocks, bpBlockSize, 0, stream>>>(
static_cast<float*>(vol.ptr),
vol.pitch,
volGeom.dim,
volGeom.voxelDim,
proj,
projPitch,
projGeom.dim,
projGeom.minCoord,
sinTheta,
cosTheta,
projGeom.pixelDim,
projGeom.d_sd,
projGeom.d_so
);
}
// [sphinx-bp-end]
}
}
// [sphinx-sync-start]
// First stream waits for other streams to complete
auto completionEvents = std::vector<hipEvent_t>{};
for(auto streamIdx = 1u; streamIdx < streams.size(); ++streamIdx)
{
auto event = hipEvent_t{};
hip_check(hipEventCreate(&event));
hip_check(hipEventRecord(event, streams.at(streamIdx)));
completionEvents.push_back(event);
}
for(auto&& event : completionEvents)
hip_check(hipStreamWaitEvent(streams.at(0), event, 0));
// [sphinx-sync-end]
// Obtain reconstruction time before copying back the result
auto stop = std::chrono::steady_clock::time_point{};
hip_check(hipLaunchHostFunc(streams.at(0), [](void* data)
{
auto& stop = *(static_cast<std::chrono::steady_clock::time_point*>(data));
stop = std::chrono::steady_clock::now();
}, static_cast<void*>(&stop)));
// Copy volume back to host and save
auto memcpyParams = hipMemcpy3DParms{};
std::memset(&memcpyParams, 0, sizeof(hipMemcpy3DParms));
memcpyParams.dstPos = make_hipPos(0, 0, 0);
memcpyParams.dstPtr = hostVol;
memcpyParams.srcPos = make_hipPos(0, 0, 0);
memcpyParams.srcPtr = vol;
memcpyParams.extent = volExt;
memcpyParams.kind = hipMemcpyDeviceToHost;
hip_check(hipMemcpy3DAsync(&memcpyParams, streams.at(0)));
auto saveVolArgs = new save_volume_args
{
"volume.tif",
hostVolPtr,
volGeom.dim.x, volGeom.dim.y, volGeom.dim.z,
volGeom.voxelDim.x, volGeom.voxelDim.y
};
hip_check(hipLaunchHostFunc(streams.at(0), save_volume, saveVolArgs));
std::cout << "All work items enqueued, waiting for completion... " << std::flush;
hip_check(hipStreamSynchronize(streams.at(0)));
std::cout << "Done!" << std::endl;
auto const elapsed = std::chrono::duration<double>{stop - start};
std::cout << "Reconstruction time: " << elapsed.count() << 's' << std::endl;
for(auto&& event : completionEvents)
hip_check(hipEventDestroy(event));
hip_check(hipFree(vol.ptr));
hip_check(hipFreeHost(hostVolPtr));
if(hasTextures)
{
for(auto&& tex : textureProjections)
hip_check(hipDestroyTextureObject(tex));
}
for(auto&& plan : backwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& plan : forwardPlans)
hipfft_check(hipfftDestroy(plan));
for(auto&& p : transformedProjections)
hip_check(hipFree(p));
for(auto&& p : expandedProjections)
hip_check(hipFree(p));
for(auto&& p : projections)
hip_check(hipFree(p));
for(auto&& p : phantomProjections)
hip_check(hipFree(p.ptr));
hip_check(hipFree(R));
for(auto&& stream : streams)
hip_check(hipStreamDestroy(stream));
hip_check(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
catch(std::runtime_error const& e)
{
std::cerr << "Caught runtime error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
@@ -0,0 +1,168 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
__global__ void kernelA(double* arrayA, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] *= 2.0;
}
}
__global__ void kernelB(int* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayB[x] = 3;
}
}
__global__ void kernelC(double* arrayA, const int* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] += arrayB[x];
}
}
struct set_vector_args
{
std::vector<double>& h_array;
double value;
};
void set_vector(void* args)
{
set_vector_args h_args{*(reinterpret_cast<set_vector_args*>(args))};
std::vector<double>& vec{h_args.h_array};
vec.assign(vec.size(), h_args.value);
}
int main()
{
constexpr int numOfBlocks = 1024;
constexpr int threadsPerBlock = 1024;
constexpr std::size_t arraySize = 1U << 20;
// This example assumes that kernelA operates on data that needs to be initialized on
// and copied from the host, while kernelB initializes the array that is passed to it.
// Both arrays are then used as input to kernelC, where arrayA is also used as
// output, that is copied back to the host, while arrayB is only read from and not modified.
double* d_arrayA;
int* d_arrayB;
std::vector<double> h_array(arraySize);
constexpr double initValue = 2.0;
hipStream_t captureStream;
HIP_CHECK(hipStreamCreate(&captureStream));
// Start capturing the operations assigned to the stream
HIP_CHECK(hipStreamBeginCapture(captureStream, hipStreamCaptureModeGlobal));
// hipMallocAsync and hipMemcpyAsync are needed, to be able to assign it to a stream
HIP_CHECK(hipMallocAsync(reinterpret_cast<void**>(&d_arrayA), arraySize*sizeof(double), captureStream));
HIP_CHECK(hipMallocAsync(reinterpret_cast<void**>(&d_arrayB), arraySize*sizeof(int), captureStream));
// Assign host function to the stream
// Needs a custom struct to pass the arguments
set_vector_args args{h_array, initValue};
HIP_CHECK(hipLaunchHostFunc(captureStream, set_vector, &args));
HIP_CHECK(hipMemcpyAsync(d_arrayA, h_array.data(), arraySize*sizeof(double), hipMemcpyHostToDevice, captureStream));
kernelA<<<numOfBlocks, threadsPerBlock, 0, captureStream>>>(d_arrayA, arraySize);
kernelB<<<numOfBlocks, threadsPerBlock, 0, captureStream>>>(d_arrayB, arraySize);
kernelC<<<numOfBlocks, threadsPerBlock, 0, captureStream>>>(d_arrayA, d_arrayB, arraySize);
HIP_CHECK(hipMemcpyAsync(h_array.data(), d_arrayA, arraySize*sizeof(*d_arrayA), hipMemcpyDeviceToHost, captureStream));
HIP_CHECK(hipFreeAsync(d_arrayA, captureStream));
HIP_CHECK(hipFreeAsync(d_arrayB, captureStream));
// Stop capturing
hipGraph_t graph;
HIP_CHECK(hipStreamEndCapture(captureStream, &graph));
// Create an executable graph from the captured graph
hipGraphExec_t graphExec;
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
// The graph template can be deleted after the instantiation if it's not needed for later use
HIP_CHECK(hipGraphDestroy(graph));
// Actually launch the graph. The stream does not have
// to be the same as the one used for capturing.
HIP_CHECK(hipGraphLaunch(graphExec, captureStream));
HIP_CHECK(hipStreamSynchronize(captureStream));
// Verify results
constexpr double expected = initValue * 2.0 + 3;
bool passed = true;
for(std::size_t i = 0; i < arraySize; ++i)
{
if(h_array[i] != expected)
{
passed = false;
std::cerr << "Validation failed! Expected " << expected << " got " << h_array[0] << std::endl;
break;
}
}
if(passed)
{
std::cerr << "Validation passed." << std::endl;
}
// Free graph and stream resources after usage
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipStreamDestroy(captureStream));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,226 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
__global__ void kernelA(double* arrayA, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] *= 2.0;
}
}
__global__ void kernelB(int* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayB[x] = 3;
}
}
__global__ void kernelC(double* arrayA, const int* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] += arrayB[x];
}
}
struct set_vector_args
{
std::vector<double>& h_array;
double value;
};
void set_vector(void* args)
{
set_vector_args h_args{*(reinterpret_cast<set_vector_args*>(args))};
std::vector<double>& vec{h_args.h_array};
vec.assign(vec.size(), h_args.value);
}
int main()
{
constexpr int numOfBlocks = 1024;
constexpr int threadsPerBlock = 1024;
std::size_t arraySize = 1U << 20;
// The pointers to the device memory don't need to be declared here,
// they are contained within the hipMemAllocNodeParams as the dptr member
std::vector<double> h_array(arraySize);
constexpr double initValue = 2.0;
// Create graph an empty graph
hipGraph_t graph;
HIP_CHECK(hipGraphCreate(&graph, 0));
// Parameters to allocate arrays
hipMemAllocNodeParams allocArrayAParams{};
allocArrayAParams.poolProps.allocType = hipMemAllocationTypePinned;
allocArrayAParams.poolProps.location.type = hipMemLocationTypeDevice;
allocArrayAParams.poolProps.location.id = 0; // GPU on which memory resides
allocArrayAParams.bytesize = arraySize * sizeof(double);
hipMemAllocNodeParams allocArrayBParams{};
allocArrayBParams.poolProps.allocType = hipMemAllocationTypePinned;
allocArrayBParams.poolProps.location.type = hipMemLocationTypeDevice;
allocArrayBParams.poolProps.location.id = 0; // GPU on which memory resides
allocArrayBParams.bytesize = arraySize * sizeof(int);
// Add the allocation nodes to the graph. They don't have any dependencies
hipGraphNode_t allocNodeA, allocNodeB;
HIP_CHECK(hipGraphAddMemAllocNode(&allocNodeA, graph, nullptr, 0, &allocArrayAParams));
HIP_CHECK(hipGraphAddMemAllocNode(&allocNodeB, graph, nullptr, 0, &allocArrayBParams));
// Parameters for the host function
// Needs custom struct to pass the arguments
set_vector_args args{h_array, initValue};
hipHostNodeParams hostParams{};
hostParams.fn = set_vector;
hostParams.userData = static_cast<void*>(&args);
// Add the host node that initializes the host array. It also doesn't have any dependencies
hipGraphNode_t hostNode;
HIP_CHECK(hipGraphAddHostNode(&hostNode, graph, nullptr, 0, &hostParams));
// Add memory copy node, that copies the initialized host array to the device.
// It has to wait for the host array to be initialized and the device memory to be allocated
hipGraphNode_t cpyNodeDependencies[] = {allocNodeA, hostNode};
hipGraphNode_t cpyToDevNode;
HIP_CHECK(hipGraphAddMemcpyNode1D(&cpyToDevNode, graph, cpyNodeDependencies, 2, allocArrayAParams.dptr, h_array.data(), arraySize * sizeof(double), hipMemcpyHostToDevice));
// Parameters for kernelA
hipKernelNodeParams kernelAParams;
void* kernelAArgs[] = {&allocArrayAParams.dptr, static_cast<void*>(&arraySize)};
kernelAParams.func = reinterpret_cast<void*>(kernelA);
kernelAParams.gridDim = numOfBlocks;
kernelAParams.blockDim = threadsPerBlock;
kernelAParams.sharedMemBytes = 0;
kernelAParams.kernelParams = kernelAArgs;
kernelAParams.extra = nullptr;
// Add the node for kernelA. It has to wait for the memory copy to finish, as it depends on the values from the host array.
hipGraphNode_t kernelANode;
HIP_CHECK(hipGraphAddKernelNode(&kernelANode, graph, &cpyToDevNode, 1, &kernelAParams));
// Parameters for kernelB
hipKernelNodeParams kernelBParams;
void* kernelBArgs[] = {&allocArrayBParams.dptr, static_cast<void*>(&arraySize)};
kernelBParams.func = reinterpret_cast<void*>(kernelB);
kernelBParams.gridDim = numOfBlocks;
kernelBParams.blockDim = threadsPerBlock;
kernelBParams.sharedMemBytes = 0;
kernelBParams.kernelParams = kernelBArgs;
kernelBParams.extra = nullptr;
// Add the node for kernelB. It only has to wait for the memory to be allocated, as it initializes the array.
hipGraphNode_t kernelBNode;
HIP_CHECK(hipGraphAddKernelNode(&kernelBNode, graph, &allocNodeB, 1, &kernelBParams));
// Parameters for kernelC
hipKernelNodeParams kernelCParams;
void* kernelCArgs[] = {&allocArrayAParams.dptr, &allocArrayBParams.dptr, static_cast<void*>(&arraySize)};
kernelCParams.func = reinterpret_cast<void*>(kernelC);
kernelCParams.gridDim = numOfBlocks;
kernelCParams.blockDim = threadsPerBlock;
kernelCParams.sharedMemBytes = 0;
kernelCParams.kernelParams = kernelCArgs;
kernelCParams.extra = nullptr;
// Add the node for kernelC. It has to wait on both kernelA and kernelB to finish, as it depends on their results.
hipGraphNode_t kernelCNode;
hipGraphNode_t kernelCDependencies[] = {kernelANode, kernelBNode};
HIP_CHECK(hipGraphAddKernelNode(&kernelCNode, graph, kernelCDependencies, 2, &kernelCParams));
// Copy the results back to the host. Has to wait for kernelC to finish.
hipGraphNode_t cpyToHostNode;
HIP_CHECK(hipGraphAddMemcpyNode1D(&cpyToHostNode, graph, &kernelCNode, 1, h_array.data(), allocArrayAParams.dptr, arraySize * sizeof(double), hipMemcpyDeviceToHost));
// Free array of allocNodeA. It needs to wait for the copy to finish, as kernelC stores its results in it.
hipGraphNode_t freeNodeA;
HIP_CHECK(hipGraphAddMemFreeNode(&freeNodeA, graph, &cpyToHostNode, 1, allocArrayAParams.dptr));
// Free array of allocNodeB. It only needs to wait for kernelC to finish, as it is not written back to the host.
hipGraphNode_t freeNodeB;
HIP_CHECK(hipGraphAddMemFreeNode(&freeNodeB, graph, &kernelCNode, 1, allocArrayBParams.dptr));
// Instantiate the graph in order to execute it
hipGraphExec_t graphExec;
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
// The graph can be freed after the instantiation if it's not needed for other purposes
HIP_CHECK(hipGraphDestroy(graph));
// Actually launch the graph
hipStream_t graphStream;
HIP_CHECK(hipStreamCreate(&graphStream));
HIP_CHECK(hipGraphLaunch(graphExec, graphStream));
HIP_CHECK(hipStreamSynchronize(graphStream));
// Verify results
constexpr double expected = initValue * 2.0 + 3;
bool passed = true;
for(std::size_t i = 0; i < arraySize; ++i)
{
if(h_array[i] != expected)
{
passed = false;
std::cerr << "Validation failed! Expected " << expected << " got " << h_array[0] << std::endl;
break;
}
}
if(passed)
{
std::cerr << "Validation passed." << std::endl;
}
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipStreamDestroy(graphStream));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,59 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
int device = 0; // Query first available GPU. Can be replaced with any
// integer up to, not including, deviceCount
hipDeviceProp_t deviceProp;
HIP_CHECK(hipGetDeviceProperties(&deviceProp, device));
std::cout << "The queried device ";
if (deviceProp.arch.hasSharedInt32Atomics) // portable HIP feature query
std::cout << "supports";
else
std::cout << "does not support";
std::cout << " shared int32 atomic operations" << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,59 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
int device = 0; // Query first available GPU. Can be replaced with any
// integer up to, not including, deviceCount
hipDeviceProp_t deviceProp;
HIP_CHECK(hipGetDeviceProperties(&deviceProp, device));
std::cout << "The queried device ";
if (deviceProp.arch.hasSharedInt32Atomics) // portable HIP feature query
std::cout << "supports";
else
std::cout << "does not support";
std::cout << " shared int32 atomic operations" << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,48 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstdlib>
int main()
{
// [sphinx-amd-start]
#ifdef __HIP_PLATFORM_AMD__
// This code path is compiled when amdclang++ is used for compilation
#endif
// [sphinx-amd-end]
// [sphinx-nvidia-start]
#ifdef __HIP_PLATFORM_NVIDIA__
// This code path is compiled when nvcc is used for compilation
// Could be compiling with CUDA language extensions enabled (for example, a ".cu file)
// Could be in pass-through mode to an underlying host compiler (for example, a .cpp file)
#endif
// [sphinx-nvidia-end]
#if !defined(__HIP_PLATFORM_AMD__) && !defined(__HIP_PLATFORM_NVIDIA__)
# error "No compatible HIP platform defined!"
#endif
return EXIT_SUCCESS;
}
@@ -0,0 +1,52 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
__host__ __device__ void call_func()
{
#ifdef __HIP_DEVICE_COMPILE__
printf("device\n");
#else
std::cout << "host" << std::endl;
#endif
}
__global__ void test_kernel()
{
call_func();
}
int main()
{
test_kernel<<<1, 1, 0, 0>>>();
if(auto err = hipDeviceSynchronize(); err != hipSuccess)
std::cerr << "HIP error " << err << ": " << hipGetErrorString(err) << std::endl;
call_func();
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,75 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
// [sphinx-kernel-start]
__global__ void kernel_memory_allocation()
{
// The pointer is stored in shared memory, so that all
// threads of the block can access the pointer
__shared__ int *memory;
std::size_t blockSize = blockDim.x;
constexpr std::size_t elementsPerThread = 1024;
if(threadIdx.x == 0)
{
// allocate memory in one contiguous block
memory = new int[blockDim.x * elementsPerThread];
}
__syncthreads();
// load pointer into thread-local variable to avoid
// unnecessary accesses to shared memory
int *localPtr = memory;
// work with allocated memory, e.g. initialization
for(int i = 0; i < elementsPerThread; ++i)
{
// access in a contiguous way
localPtr[i * blockSize + threadIdx.x] = i;
}
// synchronize to make sure no thread is accessing the memory before freeing
__syncthreads();
if(threadIdx.x == 0)
{
delete[] memory;
}
}
// [sphinx-kernel-end]
int main()
{
kernel_memory_allocation<<<64, 1024>>>();
HIP_CHECK(hipGetLastError());
std::cout << "Success!" << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,91 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " << hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Performs a simple initialization of an array with the thread's index variables.
// This function is only available in device code.
__device__ void init_array(float * const a, const unsigned int arraySize)
{
// globalIdx uniquely identifies a thread in a 1D launch configuration.
const int globalIdx = threadIdx.x + blockIdx.x * blockDim.x;
// Each thread initializes a single element of the array.
if(globalIdx < arraySize)
{
a[globalIdx] = globalIdx;
}
}
// Rounds a value up to the next multiple.
// This function is available in host and device code.
__host__ __device__ constexpr int round_up_to_nearest_multiple(int number, int multiple)
{
return (number + multiple - 1)/multiple;
}
__global__
__launch_bounds__(512, 4) // This kernel requires at most 512 threads per block and at least 4 warps per execution unit.
void example_kernel(float * const a, const unsigned int N)
{
// Initialize array.
init_array(a, N);
// Perform additional work:
// - work with the array
// - use the array in a different kernel
// - ...
}
int main()
{
constexpr int N = 100000000; // problem size
constexpr int blockSize = 256; //configurable block size
//needed number of blocks for the given problem size
constexpr int gridSize = round_up_to_nearest_multiple(N, blockSize);
float *a;
// allocate memory on the GPU
HIP_CHECK(hipMalloc(&a, sizeof(*a) * N));
std::cout << "Launching kernel." << std::endl;
example_kernel<<<dim3(gridSize), dim3(blockSize), 0/*example doesn't use shared memory*/, 0/*default stream*/>>>(a, N);
// make sure kernel execution is finished by synchronizing. The CPU can also
// execute other instructions during that time
HIP_CHECK(hipDeviceSynchronize());
std::cout << "Kernel execution finished." << std::endl;
HIP_CHECK(hipFree(a));
}
// [sphinx-end]
@@ -0,0 +1,200 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
// source code for hiprtc
static constexpr auto kernel_source{
R"(
extern "C"
__global__ void vector_add(float* output, float* input1, float* input2, size_t size)
{
int i = threadIdx.x;
if (i < size)
{
output[i] = input1[i] + input2[i];
}
}
)"};
int main()
{
hiprtcProgram prog;
auto rtc_ret_code = hiprtcCreateProgram(&prog, // HIPRTC program handle
kernel_source, // kernel source string
"vector_add.cpp", // Name of the file
0, // Number of headers
nullptr, // Header sources
nullptr); // Name of header file
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
// [sphinx-options-start]
auto sarg = std::string{"-fgpu-rdc"};
const char* compile_options[] = {sarg.c_str()};
rtc_ret_code = hiprtcCompileProgram(prog, // hiprtcProgram
1, // Number of options
compile_options);
// [sphinx-options-end]
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
std::size_t logSize;
HIPRTC_CHECK(hiprtcGetProgramLogSize(prog, &logSize));
if (logSize)
{
std::string log(logSize, '\0');
HIPRTC_CHECK(hiprtcGetProgramLog(prog, &log[0]));
std::cerr << "Compilation failed or produced warnings: " << log << std::endl;
std::abort();
}
// [sphinx-bitcode-start]
std::size_t bitCodeSize;
HIPRTC_CHECK(hiprtcGetBitcodeSize(prog, &bitCodeSize));
std::vector<char> kernel_bitcode(bitCodeSize);
HIPRTC_CHECK(hiprtcGetBitcode(prog, kernel_bitcode.data()));
// [sphinx-bitcode-end]
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
auto num_options = 0u;
hiprtcJIT_option* options = nullptr;
void* option_vals[] = {nullptr};
auto rtc_link_state = hiprtcLinkState{};
// [sphinx-link-create-start]
HIPRTC_CHECK(hiprtcLinkCreate(num_options, // number of options
options, // Array of options
option_vals, // Array of option values cast to void*
&rtc_link_state)); // HIPRTC link state created upon success
// [sphinx-link-create-end]
auto input_type = HIPRTC_JIT_INPUT_LLVM_BITCODE;
auto bit_code_ptr = kernel_bitcode.data();
auto bit_code_size = bitCodeSize;
// [sphinx-link-add-start]
HIPRTC_CHECK(hiprtcLinkAddData(rtc_link_state, // HIPRTC link state
input_type, // type of the input data or bitcode
bit_code_ptr, // input data which is null terminated
bit_code_size, // size of the input data
"a", // optional name for this input
0, // size of the options
nullptr, // Array of options applied to this input
nullptr)); // Array of option values cast to void*
// [sphinx-link-add-end]
void* binary = nullptr;
auto binarySize = std::size_t{};
// [sphinx-link-complete-start]
HIPRTC_CHECK(hiprtcLinkComplete(rtc_link_state, // HIPRTC link state
&binary, // upon success, points to the output binary
&binarySize)); // size of the binary is stored (optional)
// [sphinx-link-complete-end]
hipModule_t module;
hipFunction_t kernel;
HIP_CHECK(hipModuleLoadData(&module, binary));
HIP_CHECK(hipModuleGetFunction(&kernel, module, "vector_add"));
HIPRTC_CHECK(hiprtcLinkDestroy(rtc_link_state));
constexpr std::size_t ele_size = 256; // total number of items to add
std::vector<float> hinput, output;
hinput.reserve(ele_size);
output.reserve(ele_size);
for (std::size_t i = 0; i < ele_size; i++)
{
hinput.push_back(static_cast<float>(i + 1));
output.push_back(0.0f);
}
float *dinput1, *dinput2, *doutput;
HIP_CHECK(hipMalloc(&dinput1, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&dinput2, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&doutput, sizeof(float) * ele_size));
HIP_CHECK(hipMemcpy(dinput1, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dinput2, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
struct
{
float* output;
float* input1;
float* input2;
std::size_t size;
} args{doutput, dinput1, dinput2, ele_size};
auto size = sizeof(args);
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, &args, HIP_LAUNCH_PARAM_BUFFER_SIZE, &size,
HIP_LAUNCH_PARAM_END};
HIP_CHECK(hipModuleLaunchKernel(kernel, 1, 1, 1, ele_size, 1, 1, 0, nullptr, nullptr, config));
HIP_CHECK(hipMemcpy(output.data(), doutput, sizeof(float) * ele_size, hipMemcpyDeviceToHost));
for (std::size_t i = 0; i < ele_size; i++)
{
if ((hinput[i] + hinput[i]) != output[i])
{
std::cout << "Failed in validation: " << (hinput[i] + hinput[i]) << " - " << output[i] << std::endl;
std::abort();
}
}
std::cout << "Passed" << std::endl;
HIP_CHECK(hipFree(dinput1));
HIP_CHECK(hipFree(dinput2));
HIP_CHECK(hipFree(doutput));
return EXIT_SUCCESS;
}
// [sphinx-stop]
@@ -0,0 +1,219 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <fstream>
#include <ios>
#include <iostream>
#include <string>
#include <vector>
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
static_assert(false, "filesystem not available");
#endif
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
// source code for hiprtc
static constexpr auto kernel_source{
R"(
extern "C"
__global__ void vector_add(float* output, float* input1, float* input2, size_t size)
{
int i = threadIdx.x;
if (i < size)
{
output[i] = input1[i] + input2[i];
}
}
)"};
int main()
{
hiprtcProgram prog;
auto rtc_ret_code = hiprtcCreateProgram(&prog, // HIPRTC program handle
kernel_source, // kernel source string
"vector_add.cpp", // Name of the file
0, // Number of headers
nullptr, // Header sources
nullptr); // Name of header file
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
// [sphinx-options-start]
auto sarg = std::string{"-fgpu-rdc"};
const char* compile_options[] = {sarg.c_str()};
rtc_ret_code = hiprtcCompileProgram(prog, // hiprtcProgram
1, // Number of options
compile_options);
// [sphinx-options-end]
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
std::size_t logSize;
HIPRTC_CHECK(hiprtcGetProgramLogSize(prog, &logSize));
if (logSize)
{
std::string log(logSize, '\0');
HIPRTC_CHECK(hiprtcGetProgramLog(prog, &log[0]));
std::cerr << "Compilation failed or produced warnings: " << log << std::endl;
std::abort();
}
// [sphinx-bitcode-start]
std::size_t bitCodeSize;
HIPRTC_CHECK(hiprtcGetBitcodeSize(prog, &bitCodeSize));
std::vector<char> kernel_bitcode(bitCodeSize);
HIPRTC_CHECK(hiprtcGetBitcode(prog, kernel_bitcode.data()));
// [sphinx-bitcode-end]
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
auto num_options = 0u;
hiprtcJIT_option* options = nullptr;
void* option_vals[] = {nullptr};
auto rtc_link_state = hiprtcLinkState{};
// [sphinx-link-create-start]
HIPRTC_CHECK(hiprtcLinkCreate(num_options, // number of options
options, // Array of options
option_vals, // Array of option values cast to void*
&rtc_link_state)); // HIPRTC link state created upon success
// [sphinx-link-create-end]
auto input_type = HIPRTC_JIT_INPUT_LLVM_BITCODE;
auto bc_file_path = std::string{"bitcode.bc"};
auto bc_file = std::fstream{bc_file_path.c_str(), std::ios::binary | std::ios::out};
if(!bc_file.is_open())
{
std::cerr << "Could not open bitcode file for writing!" << std::endl;
std::abort();
}
bc_file.write(kernel_bitcode.data(), bitCodeSize);
bc_file.close();
// [sphinx-link-add-start]
HIPRTC_CHECK(hiprtcLinkAddFile(rtc_link_state, // HIPRTC link state
input_type, // type of the input data or bitcode
bc_file_path.c_str(), // input data which is null terminated
0, // size of the options
nullptr, // Array of options applied to this input
nullptr)); // Array of option values cast to void*
// [sphinx-link-add-end]
fs::remove(bc_file_path);
void* binary = nullptr;
auto binarySize = std::size_t{};
// [sphinx-link-complete-start]
HIPRTC_CHECK(hiprtcLinkComplete(rtc_link_state, // HIPRTC link state
&binary, // upon success, points to the output binary
&binarySize)); // size of the binary is stored (optional)
// [sphinx-link-complete-end]
hipModule_t module;
hipFunction_t kernel;
HIP_CHECK(hipModuleLoadData(&module, binary));
HIP_CHECK(hipModuleGetFunction(&kernel, module, "vector_add"));
HIPRTC_CHECK(hiprtcLinkDestroy(rtc_link_state));
constexpr std::size_t ele_size = 256; // total number of items to add
std::vector<float> hinput, output;
hinput.reserve(ele_size);
output.reserve(ele_size);
for (std::size_t i = 0; i < ele_size; i++)
{
hinput.push_back(static_cast<float>(i + 1));
output.push_back(0.0f);
}
float *dinput1, *dinput2, *doutput;
HIP_CHECK(hipMalloc(&dinput1, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&dinput2, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&doutput, sizeof(float) * ele_size));
HIP_CHECK(hipMemcpy(dinput1, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dinput2, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
struct
{
float* output;
float* input1;
float* input2;
std::size_t size;
} args{doutput, dinput1, dinput2, ele_size};
auto size = sizeof(args);
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, &args, HIP_LAUNCH_PARAM_BUFFER_SIZE, &size,
HIP_LAUNCH_PARAM_END};
HIP_CHECK(hipModuleLaunchKernel(kernel, 1, 1, 1, ele_size, 1, 1, 0, nullptr, nullptr, config));
HIP_CHECK(hipMemcpy(output.data(), doutput, sizeof(float) * ele_size, hipMemcpyDeviceToHost));
for (std::size_t i = 0; i < ele_size; i++)
{
if ((hinput[i] + hinput[i]) != output[i])
{
std::cout << "Failed in validation: " << (hinput[i] + hinput[i]) << " - " << output[i] << std::endl;
std::abort();
}
}
std::cout << "Passed" << std::endl;
HIP_CHECK(hipFree(dinput1));
HIP_CHECK(hipFree(dinput2));
HIP_CHECK(hipFree(doutput));
return EXIT_SUCCESS;
}
// [sphinx-stop]
@@ -0,0 +1,200 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
// source code for hiprtc
static constexpr auto kernel_source{
R"(
extern "C"
__global__ void vector_add(float* output, float* input1, float* input2, size_t size)
{
int i = threadIdx.x;
if (i < size)
{
output[i] = input1[i] + input2[i];
}
}
)"};
int main()
{
hiprtcProgram prog;
auto rtc_ret_code = hiprtcCreateProgram(&prog, // HIPRTC program handle
kernel_source, // kernel source string
"vector_add.cpp", // Name of the file
0, // Number of headers
nullptr, // Header sources
nullptr); // Name of header file
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
// [sphinx-options-start]
auto sarg = std::string{"-fgpu-rdc"};
const char* compile_options[] = {sarg.c_str()};
rtc_ret_code = hiprtcCompileProgram(prog, // hiprtcProgram
1, // Number of options
compile_options);
// [sphinx-options-end]
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
std::size_t logSize;
HIPRTC_CHECK(hiprtcGetProgramLogSize(prog, &logSize));
if (logSize)
{
std::string log(logSize, '\0');
HIPRTC_CHECK(hiprtcGetProgramLog(prog, &log[0]));
std::cerr << "Compilation failed or produced warnings: " << log << std::endl;
std::abort();
}
// [sphinx-bitcode-start]
std::size_t bitCodeSize;
HIPRTC_CHECK(hiprtcGetBitcodeSize(prog, &bitCodeSize));
std::vector<char> kernel_bitcode(bitCodeSize);
HIPRTC_CHECK(hiprtcGetBitcode(prog, kernel_bitcode.data()));
// [sphinx-bitcode-end]
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
// [sphinx-link-create-start]
const char* isaopts[] = {"-mllvm", "-inline-threshold=1", "-mllvm", "-inlinehint-threshold=1"};
std::vector<hiprtcJIT_option> jit_options = {HIPRTC_JIT_IR_TO_ISA_OPT_EXT,
HIPRTC_JIT_IR_TO_ISA_OPT_COUNT_EXT};
std::size_t isaoptssize = 4;
void* lopts[] = {reinterpret_cast<void*>(isaopts),
reinterpret_cast<void*>(isaoptssize)};
hiprtcLinkState linkstate;
HIPRTC_CHECK(hiprtcLinkCreate(2u, jit_options.data(), reinterpret_cast<void**>(lopts), &linkstate));
// [sphinx-link-create-end]
auto input_type = HIPRTC_JIT_INPUT_LLVM_BITCODE;
auto bit_code_ptr = kernel_bitcode.data();
auto bit_code_size = bitCodeSize;
// [sphinx-link-add-start]
HIPRTC_CHECK(hiprtcLinkAddData(linkstate, // HIPRTC link state
input_type, // type of the input data or bitcode
bit_code_ptr, // input data which is null terminated
bit_code_size, // size of the input data
"a", // optional name for this input
0, // size of the options
nullptr, // Array of options applied to this input
nullptr)); // Array of option values cast to void*
// [sphinx-link-add-end]
void* binary = nullptr;
auto binarySize = std::size_t{};
// [sphinx-link-complete-start]
HIPRTC_CHECK(hiprtcLinkComplete(linkstate, // HIPRTC link state
&binary, // upon success, points to the output binary
&binarySize)); // size of the binary is stored (optional)
// [sphinx-link-complete-end]
hipModule_t module;
hipFunction_t kernel;
HIP_CHECK(hipModuleLoadData(&module, binary));
HIP_CHECK(hipModuleGetFunction(&kernel, module, "vector_add"));
HIPRTC_CHECK(hiprtcLinkDestroy(linkstate));
constexpr std::size_t ele_size = 256; // total number of items to add
std::vector<float> hinput, output;
hinput.reserve(ele_size);
output.reserve(ele_size);
for (std::size_t i = 0; i < ele_size; i++)
{
hinput.push_back(static_cast<float>(i + 1));
output.push_back(0.0f);
}
float *dinput1, *dinput2, *doutput;
HIP_CHECK(hipMalloc(&dinput1, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&dinput2, sizeof(float) * ele_size));
HIP_CHECK(hipMalloc(&doutput, sizeof(float) * ele_size));
HIP_CHECK(hipMemcpy(dinput1, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(dinput2, hinput.data(), sizeof(float) * ele_size, hipMemcpyHostToDevice));
struct
{
float* output;
float* input1;
float* input2;
std::size_t size;
} args{doutput, dinput1, dinput2, ele_size};
auto size = sizeof(args);
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, &args, HIP_LAUNCH_PARAM_BUFFER_SIZE, &size,
HIP_LAUNCH_PARAM_END};
HIP_CHECK(hipModuleLaunchKernel(kernel, 1, 1, 1, ele_size, 1, 1, 0, nullptr, nullptr, config));
HIP_CHECK(hipMemcpy(output.data(), doutput, sizeof(float) * ele_size, hipMemcpyDeviceToHost));
for (std::size_t i = 0; i < ele_size; i++)
{
if ((hinput[i] + hinput[i]) != output[i])
{
std::cout << "Failed in validation: " << (hinput[i] + hinput[i]) << " - " << output[i] << std::endl;
std::abort();
}
}
std::cout << "Passed" << std::endl;
HIP_CHECK(hipFree(dinput1));
HIP_CHECK(hipFree(dinput2));
HIP_CHECK(hipFree(doutput));
return EXIT_SUCCESS;
}
// [sphinx-stop]
@@ -0,0 +1,107 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
std::size_t elements = 64*1024;
std::size_t size_bytes = elements * sizeof(float);
std::vector<float> A(elements), B(elements);
// On NVIDIA platforms the driver runtime needs to be initiated
#ifdef __HIP_PLATFORM_NVIDIA__
hipInit(0);
hipDevice_t device;
hipCtx_t context;
HIP_CHECK(hipDeviceGet(&device, 0));
HIP_CHECK(hipCtxCreate(&context, 0, device));
#endif
// Allocate device memory
hipDeviceptr_t d_A, d_B;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&d_A), size_bytes));
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&d_B), size_bytes));
// Copy data to device
HIP_CHECK(hipMemcpyHtoD(d_A, A.data(), size_bytes));
HIP_CHECK(hipMemcpyHtoD(d_B, B.data(), size_bytes));
// Load module
hipModule_t Module;
// For AMD the module file has to contain architecture specific object code
// For NVIDIA the module file has to contain PTX, found in e.g. "vcpy_isa.ptx"
#ifdef __HIP_PLATFORM_AMD__
HIP_CHECK(hipModuleLoad(&Module, "vcpy_isa.hsaco"));
#elif defined(__HIP_PLATFORM_NVIDIA__)
HIP_CHECK(hipModuleLoad(&Module, "vcpy_isa.ptx"));
#endif
// Get kernel function from the module via its name
hipFunction_t Function;
HIP_CHECK(hipModuleGetFunction(&Function, Module, "hello_world"));
// Create buffer for kernel arguments
std::vector<void*> argBuffer{reinterpret_cast<void*>(d_A), reinterpret_cast<void*>(d_B)};
std::size_t arg_size_bytes = argBuffer.size() * sizeof(void*);
// Create configuration passed to the kernel as arguments
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, argBuffer.data(),
HIP_LAUNCH_PARAM_BUFFER_SIZE, &arg_size_bytes,
HIP_LAUNCH_PARAM_END};
int threads_per_block = 128;
int blocks = (elements + threads_per_block - 1) / threads_per_block;
// Actually launch kernel
HIP_CHECK(hipModuleLaunchKernel(Function, blocks, 1, 1, threads_per_block, 1, 1, 0, 0, NULL, config));
HIP_CHECK(hipMemcpyDtoH(A.data(), d_A, elements));
HIP_CHECK(hipMemcpyDtoH(B.data(), d_B, elements));
HIP_CHECK(hipFree(reinterpret_cast<void*>(d_A)));
HIP_CHECK(hipFree(reinterpret_cast<void*>(d_B)));
#ifdef __HIP_PLATFORM_NVIDIA__
HIP_CHECK(hipCtxDestroy(context));
#endif
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,145 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
void* populate_data_pointer()
{
#ifdef __HIP_PLATFORM_AMD__
auto filename = std::string{"myKernel.hsaco"};
#elif defined(__HIP_PLATFORM_NVIDIA__)
auto filename = std::string{"myKernel.ptx"};
#endif
std::fstream file{filename, std::ios::in | std::ios::binary | std::ios::ate};
if(!file.is_open())
{
std::cerr << "Error opening file " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
auto filesize = file.tellg();
auto storage = new char[filesize];
file.seekg(0, std::ios::beg);
file.read(storage, filesize);
return storage;
}
int main()
{
std::size_t elements = 64*1024;
std::size_t size_bytes = elements * sizeof(float);
std::vector<float> A(elements), B(elements);
// On NVIDIA platforms the driver runtime needs to be initiated
#ifdef __HIP_PLATFORM_NVIDIA__
HIP_CHECK(hipInit(0));
hipDevice_t device;
hipCtx_t context;
HIP_CHECK(hipDeviceGet(&device, 0));
HIP_CHECK(hipCtxCreate(&context, 0, device));
#endif
// Allocate device memory
hipDeviceptr_t d_A, d_B;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&d_A), size_bytes));
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&d_B), size_bytes));
// Copy data to device
HIP_CHECK(hipMemcpyHtoD(d_A, A.data(), size_bytes));
HIP_CHECK(hipMemcpyHtoD(d_B, B.data(), size_bytes));
// Load module
// For AMD the module file has to contain architecture specific object code
// For NVIDIA the module file has to contain PTX, found in e.g. "myKernel.ptx"
// [sphinx-start]
hipModule_t module;
void* imagePtr = populate_data_pointer();
const int numOptions = 1;
hipJitOption options[numOptions];
void *optionValues[numOptions];
options[0] = hipJitOptionMaxRegisters;
unsigned maxRegs = 15;
optionValues[0] = static_cast<void*>(&maxRegs);
// hipModuleLoadData(module, imagePtr) will be called on HIP-Clang path, JIT options will not be used, and
// cuModuleLoadDataEx(module, imagePtr, numOptions, options, optionValues) will be called on NVCC path
HIP_CHECK(hipModuleLoadDataEx(&module, imagePtr, numOptions, options, optionValues));
// Get kernel function from the module via its name
hipFunction_t k;
HIP_CHECK(hipModuleGetFunction(&k, module, "myKernel"));
// [sphinx-end]
// Create buffer for kernel arguments
std::vector<void*> argBuffer{reinterpret_cast<void*>(d_A), reinterpret_cast<void*>(d_B)};
std::size_t arg_size_bytes = argBuffer.size() * sizeof(void*);
// Create configuration passed to the kernel as arguments
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, argBuffer.data(),
HIP_LAUNCH_PARAM_BUFFER_SIZE, &arg_size_bytes,
HIP_LAUNCH_PARAM_END};
int threads_per_block = 128;
int blocks = (elements + threads_per_block - 1) / threads_per_block;
// Actually launch kernel
HIP_CHECK(hipModuleLaunchKernel(k, blocks, 1, 1, threads_per_block, 1, 1, 0, 0, NULL, config));
HIP_CHECK(hipMemcpyDtoH(A.data(), d_A, elements));
HIP_CHECK(hipMemcpyDtoH(B.data(), d_B, elements));
HIP_CHECK(hipFree(reinterpret_cast<void*>(d_A)));
HIP_CHECK(hipFree(reinterpret_cast<void*>(d_B)));
#ifdef __HIP_PLATFORM_NVIDIA__
HIP_CHECK(hipCtxDestroy(context));
#endif
delete[] static_cast<char*>(imagePtr);
return EXIT_SUCCESS;
}
@@ -0,0 +1,134 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <cuda.h>
#include <cstddef>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#define CUDA_CHECK(expression) \
{ \
const CUresult err = expression; \
if (err != CUDA_SUCCESS) \
{ \
const char* err_str{nullptr}; \
cuGetErrorString(err, &err_str); \
std::cerr << "CUDA Error: " << err_str \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
void* populate_data_pointer()
{
auto filename = std::string{"myKernel.ptx"};
std::fstream file{filename, std::ios::in | std::ios::binary | std::ios::ate};
if(!file.is_open())
{
std::cerr << "Error opening file " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
auto filesize = file.tellg();
auto storage = new char[filesize];
file.seekg(0, std::ios::beg);
file.read(storage, filesize);
return storage;
}
int main()
{
std::size_t elements = 64*1024;
std::size_t size_bytes = elements * sizeof(float);
std::vector<float> A(elements), B(elements);
// On NVIDIA platforms the driver runtime needs to be initiated
cuInit(0);
CUdevice device;
CUcontext context;
CUDA_CHECK(cuDeviceGet(&device, 0));
CUDA_CHECK(cuCtxCreate(&context, 0, device));
// Allocate device memory
CUdeviceptr d_A, d_B;
CUDA_CHECK(cuMemAlloc(&d_A, size_bytes));
CUDA_CHECK(cuMemAlloc(&d_B, size_bytes));
// Copy data to device
CUDA_CHECK(cuMemcpyHtoD(d_A, A.data(), size_bytes));
CUDA_CHECK(cuMemcpyHtoD(d_B, B.data(), size_bytes));
// Load module
// For NVIDIA the module file has to contain PTX, found in e.g. "myKernel.ptx"
// [sphinx-start]
CUmodule module;
void* imagePtr = populate_data_pointer();
const int numOptions = 1;
CUjit_option options[numOptions];
void *optionValues[numOptions];
options[0] = CU_JIT_MAX_REGISTERS;
unsigned maxRegs = 15;
optionValues[0] = (void *)(&maxRegs);
cuModuleLoadDataEx(&module, imagePtr, numOptions, options, optionValues);
CUfunction k;
cuModuleGetFunction(&k, module, "myKernel");
// [sphinx-end]
// Create buffer for kernel arguments
std::vector<void*> argBuffer{&d_A, &d_B};
std::size_t arg_size_bytes = argBuffer.size() * sizeof(void*);
// Create configuration passed to the kernel as arguments
void* config[] = {CU_LAUNCH_PARAM_BUFFER_POINTER, argBuffer.data(),
CU_LAUNCH_PARAM_BUFFER_SIZE, &arg_size_bytes, CU_LAUNCH_PARAM_END};
int threads_per_block = 128;
int blocks = (elements + threads_per_block - 1) / threads_per_block;
// Actually launch kernel
CUDA_CHECK(cuLaunchKernel(k, blocks, 1, 1, threads_per_block, 1, 1, 0, 0, NULL, config));
CUDA_CHECK(cuMemcpyDtoH(A.data(), d_A, elements));
CUDA_CHECK(cuMemcpyDtoH(B.data(), d_B, elements));
CUDA_CHECK(cuMemFree(d_A));
CUDA_CHECK(cuMemFree(d_B));
CUDA_CHECK(cuCtxDestroy(context));
delete[] static_cast<char*>(imagePtr);
return EXIT_SUCCESS;
}
@@ -0,0 +1,111 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_fp16.h>
#include <hip/hip_runtime.h>
#include <iostream>
#include <vector>
#define hip_check(hip_call) \
{ \
auto hip_res = hip_call; \
if (hip_res != hipSuccess) { \
std::cerr << "Failed in HIP call: " << #hip_call \
<< " at " << __FILE__ << ":" << __LINE__ \
<< " with error: " << hipGetErrorString(hip_res) << std::endl; \
std::abort(); \
} \
}
__global__ void add_half_precision(__half* in1, __half* in2, float* out, size_t size)
{
int idx = threadIdx.x;
if (idx < size)
{
// Load as half, perform addition in float, store as float
float sum = __half2float(in1[idx] + in2[idx]);
out[idx] = sum;
}
}
int main()
{
constexpr size_t size = 32;
constexpr float tolerance = 1e-1f; // Allowable numerical difference
// Initialize input vectors as floats
std::vector<float> in1(size), in2(size);
for (size_t i = 0; i < size; i++) {
in1[i] = i + 1.1f;
in2[i] = i + 2.2f;
}
// Compute expected results in full precision on CPU
std::vector<float> cpu_out(size);
for (size_t i = 0; i < size; i++) {
cpu_out[i] = in1[i] + in2[i]; // Direct float addition
}
// Allocate device memory (store input as half, output as float)
__half *d_in1, *d_in2;
float *d_out;
hip_check(hipMalloc(&d_in1, sizeof(__half) * size));
hip_check(hipMalloc(&d_in2, sizeof(__half) * size));
hip_check(hipMalloc(&d_out, sizeof(float) * size));
// Convert input to half and copy to device
std::vector<__half> in1_half(size), in2_half(size);
for (size_t i = 0; i < size; i++)
{
in1_half[i] = __float2half(in1[i]);
in2_half[i] = __float2half(in2[i]);
}
hip_check(hipMemcpy(d_in1, in1_half.data(), sizeof(__half) * size, hipMemcpyHostToDevice));
hip_check(hipMemcpy(d_in2, in2_half.data(), sizeof(__half) * size, hipMemcpyHostToDevice));
// Launch kernel
add_half_precision<<<1, size>>>(d_in1, d_in2, d_out, size);
// Copy result back to host
std::vector<float> gpu_out(size, 0.0f);
hip_check(hipMemcpy(gpu_out.data(), d_out, sizeof(float) * size, hipMemcpyDeviceToHost));
// Free device memory
hip_check(hipFree(d_in1));
hip_check(hipFree(d_in2));
hip_check(hipFree(d_out));
// Validation with tolerance
for (size_t i = 0; i < size; i++)
{
if (std::fabs(cpu_out[i] - gpu_out[i]) > tolerance)
{
std::cerr << "Mismatch at index " << i
<< ": CPU result = " << cpu_out[i]
<< ", GPU result = " << gpu_out[i] << std::endl;
std::abort();
}
}
std::cout << "Success: CPU and GPU half-precision addition match within tolerance!" << std::endl;
}
@@ -0,0 +1,130 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_fp8.h>
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#include <vector>
#define hip_check(hip_call) \
{ \
auto hip_res = hip_call; \
if (hip_res != hipSuccess) \
{ \
std::cerr << "Failed in HIP call: " << #hip_call \
<< " at " << __FILE__ << ":" << __LINE__ \
<< " with error: " << hipGetErrorString(hip_res) << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__device__ __hip_fp8_storage_t d_convert_float_to_fp8(float in, __hip_fp8_interpretation_t interpret, __hip_saturation_t sat)
{
return __hip_cvt_float_to_fp8(in, sat, interpret);
}
__global__ void float_to_fp8_to_float(float *in, __hip_fp8_interpretation_t interpret, __hip_saturation_t sat, float *out, size_t size)
{
int i = threadIdx.x;
if (i < size)
{
auto fp8 = d_convert_float_to_fp8(in[i], interpret, sat);
// Implicit conversion from fp8 to float is defined by HIP runtime
out[i] = fp8;
}
}
__hip_fp8_storage_t convert_float_to_fp8(float in, /* Input val */
__hip_fp8_interpretation_t interpret, /* interpretation of number E4M3/E5M2 */
__hip_saturation_t sat /* Saturation behavior */
)
{
return __hip_cvt_float_to_fp8(in, sat, interpret);
}
int main()
{
constexpr size_t size = 32;
hipDeviceProp_t prop;
hip_check(hipGetDeviceProperties(&prop, 0));
bool is_supported = (std::string(prop.gcnArchName).find("gfx94") != std::string::npos); // gfx94x
if(!is_supported)
{
std::cerr << "Need a gfx94x, but found: " << prop.gcnArchName << std::endl;
std::cerr << "No device conversions are supported, only host conversions are supported." << std::endl;
return EXIT_SUCCESS;
}
const __hip_fp8_interpretation_t interpret = (std::string(prop.gcnArchName).find("gfx94") != std::string::npos)
? __HIP_E4M3_FNUZ // gfx94x
: __HIP_E4M3;
constexpr __hip_saturation_t sat = __HIP_SATFINITE;
std::vector<float> in;
in.reserve(size);
for (size_t i = 0; i < size; i++)
in.push_back(i + 1.1f);
std::cout << "Converting float to fp8 and back..." << std::endl;
// CPU convert
std::vector<float> cpu_out;
cpu_out.reserve(size);
for (const auto &fval : in)
{
auto fp8 = convert_float_to_fp8(fval, interpret, sat);
// Implicit conversion from fp8 to float is defined by HIP runtime
cpu_out.push_back(fp8);
}
// GPU convert
float *d_in, *d_out;
hip_check(hipMalloc(&d_in, sizeof(float) * size));
hip_check(hipMalloc(&d_out, sizeof(float) * size));
hip_check(hipMemcpy(d_in, in.data(), sizeof(float) * in.size(), hipMemcpyHostToDevice));
float_to_fp8_to_float<<<1, size>>>(d_in, interpret, sat, d_out, size);
std::vector<float> gpu_out(size, 0.0f);
hip_check(hipMemcpy(gpu_out.data(), d_out, sizeof(float) * gpu_out.size(), hipMemcpyDeviceToHost));
hip_check(hipFree(d_in));
hip_check(hipFree(d_out));
// Validation
for (size_t i = 0; i < size; i++)
{
if (cpu_out[i] != gpu_out[i])
{
std::cerr << "cpu round trip result: " << cpu_out[i]
<< " - gpu round trip result: " << gpu_out[i] << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "...CPU and GPU round trip convert matches." << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,202 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
// [sphinx-source-start]
static constexpr const char gpu_program[] {
R"(
__device__ int V1; // set from host code
static __global__ void f1(int *result)
{
*result = V1 + 10;
}
namespace N1
{
namespace N2
{
__constant__ int V2; // set from host code
__global__ void f2(int *result)
{
*result = V2 + 20;
}
}
}
template<typename T>
__global__ void f3(int *result)
{
*result = sizeof(T);
}
)"};
// [sphinx-source-end]
int main()
{
using namespace std::string_literals;
hiprtcProgram prog;
HIPRTC_CHECK(hiprtcCreateProgram(&prog, gpu_program, "gpu_source.cpp", 0, nullptr, nullptr));
std::vector<std::string> kernel_names;
std::vector<std::string> variable_names;
std::vector<int> initial_values;
std::vector<int> expected_results;
initial_values.emplace_back(100);
initial_values.emplace_back(200);
expected_results.emplace_back(110);
expected_results.emplace_back(220);
expected_results.emplace_back(static_cast<int>(sizeof(int)));
// [sphinx-add-expression-start]
kernel_names.emplace_back("&f1"s);
kernel_names.emplace_back("N1::N2::f2"s);
kernel_names.emplace_back("f3<int>"s);
for(auto&& name : kernel_names)
HIPRTC_CHECK(hiprtcAddNameExpression(prog, name.c_str()));
variable_names.emplace_back("&V1"s);
variable_names.emplace_back("&N1::N2::V2");
for(auto&& name : variable_names)
HIPRTC_CHECK(hiprtcAddNameExpression(prog, name.c_str()));
// [sphinx-add-expression-end]
hipDeviceProp_t props;
int device = 0;
HIP_CHECK(hipGetDeviceProperties(&props, device));
auto sarg = std::string{"--gpu-architecture="} + props.gcnArchName; // device for which binary is to be generated
const char* options[] = {sarg.c_str()};
HIPRTC_CHECK(hiprtcCompileProgram(prog, 1, options));
std::size_t logSize;
HIPRTC_CHECK(hiprtcGetProgramLogSize(prog, &logSize));
if (logSize)
{
std::string log(logSize, '\0');
HIPRTC_CHECK(hiprtcGetProgramLog(prog, &log[0]));
std::cerr << "Compilation failed or produced warnings: " << log << std::endl;
std::abort();
}
std::size_t codeSize;
HIPRTC_CHECK(hiprtcGetCodeSize(prog, &codeSize));
std::vector<char> kernel_binary(codeSize);
HIPRTC_CHECK(hiprtcGetCode(prog, kernel_binary.data()));
std::vector<std::string> lowered_kernel_names;
std::vector<std::string> lowered_variable_names;
// [sphinx-get-kernel-name-start]
for(auto&& name : kernel_names)
{
const char* lowered_name = nullptr;
HIPRTC_CHECK(hiprtcGetLoweredName(prog, name.c_str(), &lowered_name));
lowered_kernel_names.emplace_back(lowered_name);
}
// [sphinx-get-kernel-name-end]
// [sphinx-get-variable-name-start]
for(auto&& name : variable_names)
{
const char* lowered_name = nullptr;
HIPRTC_CHECK(hiprtcGetLoweredName(prog, name.c_str(), &lowered_name));
lowered_variable_names.emplace_back(lowered_name);
}
// [sphinx-get-variable-name-end]
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
hipModule_t module;
HIP_CHECK(hipModuleLoadData(&module, kernel_binary.data()));
for(auto i = std::size_t{0}; i < initial_values.size(); ++i)
{
auto name = lowered_variable_names.at(i);
auto initial_value = initial_values.at(i);
// [sphinx-update-variable-start]
hipDeviceptr_t variable_addr;
std::size_t bytes{};
HIP_CHECK(hipModuleGetGlobal(&variable_addr, &bytes, module, name.c_str()));
HIP_CHECK(hipMemcpyHtoD(variable_addr, &initial_value, sizeof(initial_value)));
// [sphinx-update-variable-end]
}
hipDeviceptr_t d_result;
auto h_result = 0;
HIP_CHECK(hipMalloc(reinterpret_cast<void**>(&d_result), sizeof(h_result)));
HIP_CHECK(hipMemcpyHtoD(d_result, &h_result, sizeof(h_result)));
struct
{
hipDeviceptr_t ptr;
} args{d_result};
auto args_size = sizeof(args);
void* config[] = {HIP_LAUNCH_PARAM_BUFFER_POINTER, &args,
HIP_LAUNCH_PARAM_BUFFER_SIZE, &args_size,
HIP_LAUNCH_PARAM_END};
for(auto i = std::size_t{0}; i < lowered_kernel_names.size(); ++i)
{
auto name = lowered_kernel_names.at(i);
auto expected = expected_results.at(i);
// [sphinx-launch-kernel-start]
hipFunction_t kernel;
HIP_CHECK(hipModuleGetFunction(&kernel, module, name.c_str()));
HIP_CHECK(hipModuleLaunchKernel(kernel, 1, 1, 1, 1, 1, 1, 0, nullptr, nullptr, config));
// [sphinx-launch-kernel-end]
HIP_CHECK(hipMemcpyDtoH(&h_result, d_result, sizeof(h_result)));
if(expected != h_result)
{
std::cerr << "Validation failed. expected = " << expected << ", h_result = " << h_result << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "Validation passed." << std::endl;
HIP_CHECK(hipFree(reinterpret_cast<void*>(d_result)));
return EXIT_SUCCESS;
}
@@ -0,0 +1,118 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cmath>
#include <iostream>
#include <limits>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
exit(EXIT_FAILURE); \
} \
}
// Simple ULP difference calculator
int64_t ulp_diff(float a, float b)
{
if (a == b)
return 0;
union
{
float f;
int32_t i;
} ua{a}, ub{b};
// For negative values, convert to a positive-based representation
if (ua.i < 0) ua.i = std::numeric_limits<int32_t>::max() - ua.i;
if (ub.i < 0) ub.i = std::numeric_limits<int32_t>::max() - ub.i;
return std::abs((int64_t)ua.i - (int64_t)ub.i);
}
// Test kernel
__global__ void test_sin(float* out, int n)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n)
{
float x = -M_PI + (2.0f * M_PI * i) / (n - 1);
out[i] = sinf(x);
}
}
int main()
{
const int n = 1000000;
const int blocksize = 256;
std::vector<float> outputs(n);
float* d_out;
HIP_CHECK(hipMalloc(&d_out, n * sizeof(float)));
dim3 threads(blocksize);
dim3 blocks((n + blocksize - 1) / blocksize); // Fixed grid calculation
test_sin<<<blocks, threads>>>(d_out, n);
HIP_CHECK(hipPeekAtLastError());
HIP_CHECK(hipMemcpy(outputs.data(), d_out, n * sizeof(float), hipMemcpyDeviceToHost));
// Step 1: Find the maximum absolute error
double max_abs_error = 0.0;
float max_error_output = 0.0;
float max_error_expected = 0.0;
for (int i = 0; i < n; i++)
{
float x = -M_PI + (2.0f * M_PI * i) / (n - 1);
float expected = std::sin(x);
double abs_error = std::abs(outputs[i] - expected);
if (abs_error > max_abs_error)
{
max_abs_error = abs_error;
max_error_output = outputs[i];
max_error_expected = expected;
}
}
// Step 2: Compute ULP difference based on the max absolute error pair
int64_t max_ulp = ulp_diff(max_error_output, max_error_expected);
// Output results
std::cout << "Max Absolute Error: " << max_abs_error << std::endl;
std::cout << "Max ULP Difference: " << max_ulp << std::endl;
std::cout << "Max Error Values -> Got: " << max_error_output
<< ", Expected: " << max_error_expected << std::endl;
HIP_CHECK(hipFree(d_out));
return 0;
}
// [sphinx-end]
@@ -0,0 +1,109 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
// Kernel to perform some computation on allocated memory.
__global__ void myKernel(int* data, std::size_t numElements)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
if (tid < numElements)
{
data[tid] = tid * 2;
}
}
int main()
{
// Create a stream.
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
// Create a memory pool with default properties.
hipMemPoolProps poolProps = {};
poolProps.allocType = hipMemAllocationTypePinned;
poolProps.handleTypes = hipMemHandleTypePosixFileDescriptor;
poolProps.location.type = hipMemLocationTypeDevice;
poolProps.location.id = 0; // Assuming device 0.
hipMemPool_t memPool;
HIP_CHECK(hipMemPoolCreate(&memPool, &poolProps));
// Allocate memory from the pool asynchronously.
constexpr std::size_t numElements = 1024;
int* devData = nullptr;
HIP_CHECK(hipMallocFromPoolAsync(reinterpret_cast<void**>(&devData),
numElements * sizeof(*devData),
memPool,
stream));
// Define grid and block sizes.
dim3 blockSize(256);
dim3 gridSize((numElements + blockSize.x - 1) / blockSize.x);
// Launch the kernel to perform computation.
myKernel<<<gridSize, blockSize, 0, stream>>>(devData, numElements);
// Synchronize the stream.
HIP_CHECK(hipStreamSynchronize(stream));
// Copy data back to host.
int* hostData = new int[numElements];
HIP_CHECK(hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost));
// Print the array.
for (std::size_t i = 0; i < numElements; ++i)
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
// Free the allocated memory.
HIP_CHECK(hipFreeAsync(devData, stream));
// Synchronize the stream again to ensure all operations are complete.
HIP_CHECK(hipStreamSynchronize(stream));
// Destroy the memory pool and stream.
HIP_CHECK(hipMemPoolDestroy(memPool));
HIP_CHECK(hipStreamDestroy(stream));
// Free host memory.
delete[] hostData;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,115 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
// Sample helper functions for getting the usage statistics in bulk.
struct usageStatistics
{
std::uint64_t reservedMemCurrent;
std::uint64_t reservedMemHigh;
std::uint64_t usedMemCurrent;
std::uint64_t usedMemHigh;
};
void getUsageStatistics(hipMemPool_t memPool, struct usageStatistics *statistics)
{
HIP_CHECK(hipMemPoolGetAttribute(memPool, hipMemPoolAttrReservedMemCurrent, &statistics->reservedMemCurrent));
HIP_CHECK(hipMemPoolGetAttribute(memPool, hipMemPoolAttrReservedMemHigh, &statistics->reservedMemHigh));
HIP_CHECK(hipMemPoolGetAttribute(memPool, hipMemPoolAttrUsedMemCurrent, &statistics->usedMemCurrent));
HIP_CHECK(hipMemPoolGetAttribute(memPool, hipMemPoolAttrUsedMemHigh, &statistics->usedMemHigh));
}
// Resetting the watermarks resets them to the current value.
void resetStatistics(hipMemPool_t memPool)
{
std::uint64_t value = 0;
HIP_CHECK(hipMemPoolSetAttribute(memPool, hipMemPoolAttrReservedMemHigh, &value));
HIP_CHECK(hipMemPoolSetAttribute(memPool, hipMemPoolAttrUsedMemHigh, &value));
}
int main()
{
hipMemPool_t memPool;
hipDevice_t device = 0; // Specify the device index.
// Initialize the device.
HIP_CHECK(hipSetDevice(device));
// Get the default memory pool for the device.
HIP_CHECK(hipDeviceGetDefaultMemPool(&memPool, device));
// Allocate memory from the pool (e.g., 1 MB).
std::size_t allocSize = 1 * 1024 * 1024;
void* ptr;
HIP_CHECK(hipMalloc(&ptr, allocSize));
// Free the allocated memory.
HIP_CHECK(hipFree(ptr));
// Trim the memory pool to a specific size (e.g., 512 KB).
std::size_t newSize = 512 * 1024;
HIP_CHECK(hipMemPoolTrimTo(memPool, newSize));
// Get and print usage statistics before resetting.
usageStatistics statsBefore;
getUsageStatistics(memPool, &statsBefore);
std::cout << "Before resetting statistics:" << std::endl;
std::cout << "Reserved Memory Current: " << statsBefore.reservedMemCurrent << " bytes" << std::endl;
std::cout << "Reserved Memory High: " << statsBefore.reservedMemHigh << " bytes" << std::endl;
std::cout << "Used Memory Current: " << statsBefore.usedMemCurrent << " bytes" << std::endl;
std::cout << "Used Memory High: " << statsBefore.usedMemHigh << " bytes" << std::endl;
// Reset the statistics.
resetStatistics(memPool);
// Get and print usage statistics after resetting.
usageStatistics statsAfter;
getUsageStatistics(memPool, &statsAfter);
std::cout << "After resetting statistics:" << std::endl;
std::cout << "Reserved Memory Current: " << statsAfter.reservedMemCurrent << " bytes" << std::endl;
std::cout << "Reserved Memory High: " << statsAfter.reservedMemHigh << " bytes" << std::endl;
std::cout << "Used Memory Current: " << statsAfter.usedMemCurrent << " bytes" << std::endl;
std::cout << "Used Memory High: " << statsAfter.usedMemHigh << " bytes" << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,115 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <limits>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
// Kernel to perform some computation on allocated memory.
__global__ void myKernel(int* data, std::size_t numElements)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
if (tid < numElements)
{
data[tid] = tid * 2;
}
}
int main()
{
// Create a stream.
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
// Create a memory pool with default properties.
hipMemPoolProps poolProps = {};
poolProps.allocType = hipMemAllocationTypePinned;
poolProps.handleTypes = hipMemHandleTypePosixFileDescriptor;
poolProps.location.type = hipMemLocationTypeDevice;
poolProps.location.id = 0; // Assuming device 0.
hipMemPool_t memPool;
HIP_CHECK(hipMemPoolCreate(&memPool, &poolProps));
// [sphinx-start]
std::uint64_t threshold = std::numeric_limits<std::uint64_t>::max();
HIP_CHECK(hipMemPoolSetAttribute(memPool, hipMemPoolAttrReleaseThreshold, &threshold));
// [sphinx-end]
// Allocate memory from the pool asynchronously.
constexpr std::size_t numElements = 1024;
int* devData = nullptr;
HIP_CHECK(hipMallocFromPoolAsync(reinterpret_cast<void**>(&devData),
numElements * sizeof(*devData),
memPool,
stream));
// Define grid and block sizes.
dim3 blockSize(256);
dim3 gridSize((numElements + blockSize.x - 1) / blockSize.x);
// Launch the kernel to perform computation.
myKernel<<<gridSize, blockSize, 0, stream>>>(devData, numElements);
// Synchronize the stream.
HIP_CHECK(hipStreamSynchronize(stream));
// Copy data back to host.
int* hostData = new int[numElements];
HIP_CHECK(hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost));
// Print the array.
for (std::size_t i = 0; i < numElements; ++i)
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
// Free the allocated memory.
HIP_CHECK(hipFreeAsync(devData, stream));
// Synchronize the stream again to ensure all operations are complete.
HIP_CHECK(hipStreamSynchronize(stream));
// Destroy the memory pool and stream.
HIP_CHECK(hipMemPoolDestroy(memPool));
HIP_CHECK(hipStreamDestroy(stream));
// Free host memory.
delete[] hostData;
return 0;
}
@@ -0,0 +1,69 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
hipMemPool_t memPool;
hipDevice_t device = 0; // Specify the device index.
// Initialize the device.
HIP_CHECK(hipSetDevice(device));
// Get the default memory pool for the device.
HIP_CHECK(hipDeviceGetDefaultMemPool(&memPool, device));
// Allocate memory from the pool (e.g., 1 MB).
std::size_t allocSize = 1 * 1024 * 1024;
void* ptr;
HIP_CHECK(hipMalloc(&ptr, allocSize));
// Free the allocated memory.
HIP_CHECK(hipFree(ptr));
// Trim the memory pool to a specific size (e.g., 512 KB).
std::size_t newSize = 512 * 1024;
HIP_CHECK(hipMemPoolTrimTo(memPool, newSize));
std::cout << "Memory pool trimmed to " << newSize << " bytes." << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,90 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main()
{
int *a, *b, *c;
unsigned int attributeValue;
constexpr std::size_t attributeSize = sizeof(attributeValue);
int deviceId;
HIP_CHECK(hipGetDevice(&deviceId));
// Allocate memory for a, b and c that is accessible to both device and host codes.
HIP_CHECK(hipMallocManaged(&a, sizeof(*a)));
HIP_CHECK(hipMallocManaged(&b, sizeof(*b)));
HIP_CHECK(hipMallocManaged(&c, sizeof(*c)));
// Setup input values.
*a = 1;
*b = 2;
HIP_CHECK(hipMemAdvise(a, sizeof(*a), hipMemAdviseSetReadMostly, deviceId));
// Launch add() kernel on GPU.
add<<<1, 1>>>(a, b, c);
// Wait for GPU to finish before accessing on host.
HIP_CHECK(hipDeviceSynchronize());
// Query an attribute of the memory range.
HIP_CHECK(hipMemRangeGetAttribute(&attributeValue,
attributeSize,
hipMemRangeAttributeReadMostly,
a,
sizeof(*a)));
// Prints the result.
std::cout << *a << " + " << *b << " = " << *c << std::endl;
std::cout << "The array a is" << (attributeValue == 1 ? "" : " NOT") << " set to hipMemRangeAttributeReadMostly" << std::endl;
// Cleanup allocated memory.
HIP_CHECK(hipFree(a));
HIP_CHECK(hipFree(b));
HIP_CHECK(hipFree(c));
return 0;
}
// [sphinx-end]
@@ -0,0 +1,136 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__global__ void simpleKernel(double *data, std::size_t elems)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if(idx < elems)
data[idx] = idx * 2.0;
}
int main()
{
int numDevices;
HIP_CHECK(hipGetDeviceCount(&numDevices));
if (numDevices < 2)
{
std::cout << "This example requires at least two HIP devices." << std::endl;
return EXIT_SUCCESS;
}
double *deviceData0;
double *deviceData1;
constexpr std::size_t elems = 1024;
constexpr std::size_t size = elems * sizeof(double);
// Create streams and events for each device
hipStream_t stream0, stream1;
hipEvent_t startEvent0, stopEvent0, startEvent1, stopEvent1;
// Initialize device 0
HIP_CHECK(hipSetDevice(0));
HIP_CHECK(hipStreamCreate(&stream0));
HIP_CHECK(hipEventCreate(&startEvent0));
HIP_CHECK(hipEventCreate(&stopEvent0));
HIP_CHECK(hipMalloc(&deviceData0, size));
// Initialize device 1
HIP_CHECK(hipSetDevice(1));
HIP_CHECK(hipStreamCreate(&stream1));
HIP_CHECK(hipEventCreate(&startEvent1));
HIP_CHECK(hipEventCreate(&stopEvent1));
HIP_CHECK(hipMalloc(&deviceData1, size));
// Record the start event on device 0
HIP_CHECK(hipSetDevice(0));
HIP_CHECK(hipEventRecord(startEvent0, stream0));
// Launch the kernel asynchronously on device 0
simpleKernel<<<8, 128, 0, stream0>>>(deviceData0, elems);
// Record the stop event on device 0
HIP_CHECK(hipEventRecord(stopEvent0, stream0));
// Wait for the stop event on device 0 to complete
HIP_CHECK(hipEventSynchronize(stopEvent0));
// Record the start event on device 1
HIP_CHECK(hipSetDevice(1));
HIP_CHECK(hipEventRecord(startEvent1, stream1));
// Launch the kernel asynchronously on device 1
simpleKernel<<<8, 128, 0, stream1>>>(deviceData1, elems);
// Record the stop event on device 1
HIP_CHECK(hipEventRecord(stopEvent1, stream1));
// Wait for the stop event on device 1 to complete
HIP_CHECK(hipEventSynchronize(stopEvent1));
// Calculate elapsed time between the events for both devices
float milliseconds0 = 0, milliseconds1 = 0;
HIP_CHECK(hipEventElapsedTime(&milliseconds0, startEvent0, stopEvent0));
HIP_CHECK(hipEventElapsedTime(&milliseconds1, startEvent1, stopEvent1));
std::cout << "Elapsed time on GPU 0: " << milliseconds0 << " ms" << std::endl;
std::cout << "Elapsed time on GPU 1: " << milliseconds1 << " ms" << std::endl;
// Cleanup for device 0
HIP_CHECK(hipSetDevice(0));
HIP_CHECK(hipEventDestroy(startEvent0));
HIP_CHECK(hipEventDestroy(stopEvent0));
HIP_CHECK(hipStreamSynchronize(stream0));
HIP_CHECK(hipStreamDestroy(stream0));
HIP_CHECK(hipFree(deviceData0));
// Cleanup for device 1
HIP_CHECK(hipSetDevice(1));
HIP_CHECK(hipEventDestroy(startEvent1));
HIP_CHECK(hipEventDestroy(stopEvent1));
HIP_CHECK(hipStreamSynchronize(stream1));
HIP_CHECK(hipStreamDestroy(stream1));
HIP_CHECK(hipFree(deviceData1));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,81 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
// Kernel to perform some computation on allocated memory.
__global__ void myKernel(int* data, std::size_t numElements)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
if (tid < numElements)
{
data[tid] = tid * 2;
}
}
int main()
{
// Allocate memory.
constexpr std::size_t numElements = 1024;
int* devData;
HIP_CHECK(hipMalloc(&devData, numElements * sizeof(*devData)));
// Launch the kernel to perform computation.
dim3 blockSize(256);
dim3 gridSize((numElements + blockSize.x - 1) / blockSize.x);
myKernel<<<gridSize, blockSize>>>(devData, numElements);
// Copy data back to host.
int* hostData = new int[numElements];
HIP_CHECK(hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost));
// Print the array.
for (std::size_t i = 0; i < numElements; ++i)
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
// Free memory.
HIP_CHECK(hipFree(devData));
delete[] hostData;
// Synchronize to ensure completion.
HIP_CHECK(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,114 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__global__ void simpleKernel(double *data, std::size_t elems)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if(idx < elems)
data[idx] = idx * 2.0;
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
if(deviceCount < 2)
{
std::cout << "This example requires at least two HIP devices." << std::endl;
return EXIT_SUCCESS;
}
double* deviceData0;
double* deviceData1;
constexpr std::size_t elems = 1024;
constexpr std::size_t size = elems * sizeof(double);
int deviceId0 = 0;
int deviceId1 = 1;
// Enable peer access to the memory (allocated and future) on the peer device.
// Ensure the device is active before enabling peer access.
HIP_CHECK(hipSetDevice(deviceId0));
HIP_CHECK(hipDeviceEnablePeerAccess(deviceId1, 0));
HIP_CHECK(hipSetDevice(deviceId1));
HIP_CHECK(hipDeviceEnablePeerAccess(deviceId0, 0));
// Set device 0 and perform operations
HIP_CHECK(hipSetDevice(deviceId0)); // Set device 0 as current
HIP_CHECK(hipMalloc(&deviceData0, size)); // Allocate memory on device 0
simpleKernel<<<8, 128>>>(deviceData0, elems); // Launch kernel on device 0
HIP_CHECK(hipDeviceSynchronize());
// Set device 1 and perform operations
HIP_CHECK(hipSetDevice(deviceId1)); // Set device 1 as current
HIP_CHECK(hipMalloc(&deviceData1, size)); // Allocate memory on device 1
simpleKernel<<<8, 128>>>(deviceData1, elems); // Launch kernel on device 1
HIP_CHECK(hipDeviceSynchronize());
// Use peer-to-peer access
HIP_CHECK(hipSetDevice(deviceId0));
// Now device 0 can access memory allocated on device 1
HIP_CHECK(hipMemcpy(deviceData0, deviceData1, size, hipMemcpyDeviceToDevice));
// Copy result from device 0
double hostData0[elems];
HIP_CHECK(hipSetDevice(deviceId0));
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
// Copy result from device 1
double hostData1[elems];
HIP_CHECK(hipSetDevice(deviceId1));
HIP_CHECK(hipMemcpy(hostData1, deviceData1, size, hipMemcpyDeviceToHost));
// Display results from both devices
std::cout << "Device 0 data: " << hostData0[0] << std::endl;
std::cout << "Device 1 data: " << hostData1[0] << std::endl;
// Free device memory
HIP_CHECK(hipFree(deviceData0));
HIP_CHECK(hipFree(deviceData1));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,104 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
__global__ void simpleKernel(double *data, std::size_t elems)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if(idx < elems)
data[idx] = idx * 2.0;
}
int main()
{
int deviceCount;
HIP_CHECK(hipGetDeviceCount(&deviceCount));
if(deviceCount < 2)
{
std::cout << "This example requires at least two HIP devices." << std::endl;
return EXIT_SUCCESS;
}
double* deviceData0;
double* deviceData1;
constexpr std::size_t elems = 1024;
constexpr std::size_t size = elems * sizeof(double);
int deviceId0 = 0;
int deviceId1 = 1;
// Set device 0 and perform operations
HIP_CHECK(hipSetDevice(deviceId0)); // Set device 0 as current
HIP_CHECK(hipMalloc(&deviceData0, size)); // Allocate memory on device 0
simpleKernel<<<8, 128>>>(deviceData0, elems); // Launch kernel on device 0
HIP_CHECK(hipDeviceSynchronize());
// Set device 1 and perform operations
HIP_CHECK(hipSetDevice(deviceId1)); // Set device 1 as current
HIP_CHECK(hipMalloc(&deviceData1, size)); // Allocate memory on device 1
simpleKernel<<<8, 128>>>(deviceData1, elems); // Launch kernel on device 1
HIP_CHECK(hipDeviceSynchronize());
// Use deviceData0 on device 1. This works but incurs a performance penalty.
HIP_CHECK(hipSetDevice(deviceId1));
HIP_CHECK(hipMemcpy(deviceData1, deviceData0, size, hipMemcpyDeviceToDevice));
// Copy result from device 0
double hostData0[elems];
HIP_CHECK(hipSetDevice(deviceId0));
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
// Copy result from device 1
double hostData1[elems];
HIP_CHECK(hipSetDevice(deviceId1));
HIP_CHECK(hipMemcpy(hostData1, deviceData1, size, hipMemcpyDeviceToHost));
// Display results from both devices
std::cout << "Device 0 data: " << hostData0[0] << std::endl;
std::cout << "Device 1 data: " << hostData1[0] << std::endl;
// Free device memory
HIP_CHECK(hipFree(deviceData0));
HIP_CHECK(hipFree(deviceData1));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,80 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstring>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
int main()
{
const int element_number = 100;
int *host_input, *host_output;
// Host allocation
host_input = new int[element_number];
host_output = new int[element_number];
// Host data preparation
for (int i = 0; i < element_number; i++) {
host_input[i] = i;
}
std::memset(host_output, 0, element_number * sizeof(int));
int *device_input, *device_output;
// Device allocation
HIP_CHECK(hipMalloc((int **)&device_input, element_number * sizeof(int)));
HIP_CHECK(hipMalloc((int **)&device_output, element_number * sizeof(int)));
// Device data preparation
HIP_CHECK(hipMemcpy(device_input, host_input, element_number * sizeof(int), hipMemcpyHostToDevice));
HIP_CHECK(hipMemset(device_output, 0, element_number * sizeof(int)));
// Run the kernel
// ...
HIP_CHECK(hipMemcpy(device_input, host_input, element_number * sizeof(int), hipMemcpyHostToDevice));
// Free host memory
delete[] host_input;
delete[] host_output;
// Free device memory
HIP_CHECK(hipFree(device_input));
HIP_CHECK(hipFree(device_output));
}
// [sphinx-end]
@@ -0,0 +1,78 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
int main()
{
// Initialize the HIP runtime
if (auto err = hipInit(0); err != hipSuccess)
{
std::cerr << "Failed to initialize HIP runtime." << std::endl;
return EXIT_FAILURE;
}
// Get the per-thread default stream
hipStream_t stream = hipStreamPerThread;
// Use the stream for some operation
// For example, allocate memory on the device
void* d_ptr;
std::size_t size = 1024;
if (auto err = hipMalloc(&d_ptr, size); err != hipSuccess)
{
std::cerr << "Failed to allocate memory." << std::endl;
return EXIT_FAILURE;
}
// Perform some operation using the stream
// For example, set memory on the device
if (auto err = hipMemsetAsync(d_ptr, 0, size, stream); err != hipSuccess)
{
std::cerr << "Failed to set memory." << std::endl;
return EXIT_FAILURE;
}
// Synchronize the stream
if (auto err = hipStreamSynchronize(stream); err != hipSuccess)
{
std::cerr << "Failed to synchronize stream." << std::endl;
return EXIT_FAILURE;
}
// Free the allocated memory
if(auto err = hipFree(d_ptr); err != hipSuccess)
{
std::cerr << "Failed to free memory." << std::endl;
return EXIT_FAILURE;
}
std::cout << "Operation completed successfully using per-thread default stream." << std::endl;
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,81 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstring>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
int main()
{
const int element_number = 100;
int *host_input, *host_output;
// Host allocation
HIP_CHECK(hipHostMalloc(&host_input, element_number * sizeof(int)));
HIP_CHECK(hipHostMalloc(&host_output, element_number * sizeof(int)));
// Host data preparation
for (int i = 0; i < element_number; i++)
{
host_input[i] = i;
}
std::memset(host_output, 0, element_number * sizeof(int));
int *device_input, *device_output;
// Device allocation
HIP_CHECK(hipMalloc(&device_input, element_number * sizeof(int)));
HIP_CHECK(hipMalloc(&device_output, element_number * sizeof(int)));
// Device data preparation
HIP_CHECK(hipMemcpy(device_input, host_input, element_number * sizeof(int), hipMemcpyHostToDevice));
HIP_CHECK(hipMemset(device_output, 0, element_number * sizeof(int)));
// Run the kernel
// ...
HIP_CHECK(hipMemcpy(device_input, host_input, element_number * sizeof(int), hipMemcpyHostToDevice));
// Free host memory
HIP_CHECK(hipFreeHost(host_input));
HIP_CHECK(hipFreeHost(host_output));
// Free device memory
HIP_CHECK(hipFree(device_input));
HIP_CHECK(hipFree(device_output));
}
// [sphinx-end]
@@ -0,0 +1,61 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if (err != hipSuccess) \
{ \
std::cout << "HIP Error: " << hipGetErrorString(err) \
<< " at line " << __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
int main()
{
// [sphinx-start]
double * ptr;
HIP_CHECK(hipMalloc(&ptr, sizeof(double)));
hipPointerAttribute_t attr;
HIP_CHECK(hipPointerGetAttributes(&attr, ptr)); /*attr.type is hipMemoryTypeDevice*/
if(attr.type == hipMemoryTypeDevice)
std::cout << "ptr is of type hipMemoryTypeDevice" << std::endl;
double* ptrHost;
HIP_CHECK(hipHostMalloc(&ptrHost, sizeof(double)));
hipPointerAttribute_t attrHost;
HIP_CHECK(hipPointerGetAttributes(&attrHost, ptrHost)); /*attr.type is hipMemoryTypeHost*/
if(attrHost.type == hipMemoryTypeHost)
std::cout << "ptrHost is of type hipMemoryTypeHost" << std::endl;
// [sphinx-end]
HIP_CHECK(hipFreeHost(ptrHost));
HIP_CHECK(hipFree(ptr));
return EXIT_SUCCESS;
}
@@ -0,0 +1,79 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <hip/hiprtc.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#define CHECK_RET_CODE(call, ret_code) \
{ \
if ((call) != ret_code) \
{ \
std::cout << "Failed in call: " << #call << std::endl; \
std::abort(); \
} \
}
#define HIP_CHECK(call) CHECK_RET_CODE(call, hipSuccess)
#define HIPRTC_CHECK(call) CHECK_RET_CODE(call, HIPRTC_SUCCESS)
int main()
{
const char* kernel_source = "adafsfgadascvsfgsadfbdt";
hiprtcProgram prog;
auto rtc_ret_code = hiprtcCreateProgram(&prog, // HIPRTC program handle
kernel_source, // kernel source string
"vector_add.cpp", // Name of the file
0, // Number of headers
nullptr, // Header sources
nullptr); // Name of header file
if (rtc_ret_code != HIPRTC_SUCCESS)
{
std::cerr << "Failed to create program" << std::endl;
std::abort();
}
hipDeviceProp_t props;
int device = 0;
HIP_CHECK(hipGetDeviceProperties(&props, device));
auto sarg = std::string{"--gpu-architecture="} + props.gcnArchName; // device for which binary is to be generated
const char* opts[] = {sarg.c_str()};
// [sphinx-start]
hiprtcResult result;
result = hiprtcCompileProgram(prog, 1, opts);
if (result != HIPRTC_SUCCESS)
{
std::cout << "hiprtcCompileProgram fails with error " << hiprtcGetErrorString(result);
}
// [sphinx-end]
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
return EXIT_SUCCESS;
}
@@ -0,0 +1,131 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <vector>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
// GPU Kernels
__global__ void kernelA(double* arrayA, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayA[x] += 1.0;
}
}
__global__ void kernelB(double* arrayA, double* arrayB, std::size_t size)
{
const std::size_t x = threadIdx.x + blockDim.x * blockIdx.x;
if(x < size)
{
arrayB[x] += arrayA[x] + 3.0;
}
}
int main()
{
constexpr int numOfBlocks = 1 << 20;
constexpr int threadsPerBlock = 1024;
constexpr int numberOfIterations = 50;
// The array size smaller to avoid the relatively short kernel launch compared to memory copies
constexpr std::size_t arraySize = 1U << 25;
double *d_dataA;
double *d_dataB;
double initValueA = 0.0;
double initValueB = 2.0;
std::vector<double> vectorA(arraySize, initValueA);
std::vector<double> vectorB(arraySize, initValueB);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_dataA, arraySize * sizeof(*d_dataA)));
HIP_CHECK(hipMalloc(&d_dataB, arraySize * sizeof(*d_dataB)));
for(int iteration = 0; iteration < numberOfIterations; iteration++)
{
// Host to Device copies
HIP_CHECK(hipMemcpy(d_dataA, vectorA.data(), arraySize * sizeof(*d_dataA), hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpy(d_dataB, vectorB.data(), arraySize * sizeof(*d_dataB), hipMemcpyHostToDevice));
// Launch the GPU kernels
kernelA<<<numOfBlocks, threadsPerBlock>>>(d_dataA, arraySize);
kernelB<<<numOfBlocks, threadsPerBlock>>>(d_dataA, d_dataB, arraySize);
// Device to Host copies
HIP_CHECK(hipMemcpy(vectorA.data(), d_dataA, arraySize * sizeof(*vectorA.data()), hipMemcpyDeviceToHost));
HIP_CHECK(hipMemcpy(vectorB.data(), d_dataB, arraySize * sizeof(*vectorB.data()), hipMemcpyDeviceToHost));
}
// Wait for all operations to complete
HIP_CHECK(hipDeviceSynchronize());
// Verify results
const double expectedA = (double)numberOfIterations;
const double expectedB = initValueB + (3.0 * numberOfIterations) + (expectedA * (expectedA + 1.0)) / 2.0;
bool passed = true;
for(std::size_t i = 0; i < arraySize; ++i)
{
if(vectorA[i] != expectedA)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedA << " got " << vectorA[i] << " at index: " << i << std::endl;
break;
}
if(vectorB[i] != expectedB)
{
passed = false;
std::cerr << "Validation failed! Expected " << expectedB << " got " << vectorB[i] << " at index: " << i << std::endl;
break;
}
}
if(passed)
{
std::cout << "Sequential execution completed successfully." << std::endl;
}
else
{
std::cerr << "Sequential execution failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipFree(d_dataA));
HIP_CHECK(hipFree(d_dataB));
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -0,0 +1,47 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
// [sphinx-start]
__constant__ int const_array[8];
void set_constant_memory()
{
int host_data[8] {1,2,3,4,5,6,7,8};
if(auto err = hipMemcpyToSymbol(const_array, host_data, sizeof(int) * 8); err != hipSuccess)
std::cerr << "HIP error " << err << ": " << hipGetErrorString(err) << std::endl;
// call kernel that accesses const_array
}
// [sphinx-end]
int main()
{
set_constant_memory();
std::cout << "Success!" << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,42 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
int main()
{
int deviceCount;
if (hipGetDeviceCount(&deviceCount) == hipSuccess)
{
for (int i = 0; i < deviceCount; ++i)
{
hipDeviceProp_t prop;
if (hipGetDeviceProperties(&prop, i) == hipSuccess)
std::cout << "Device" << i << prop.name << std::endl;
}
}
return 0;
}
// [sphinx-end]
@@ -0,0 +1,73 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
// This example requires HMM support and the environment variable HSA_XNACK needs to be set to 1
int main()
{
// Allocate memory for a, b, and c.
int *a = new int[1];
int *b = new int[1];
int *c = new int[1];
// Setup input values.
*a = 1;
*b = 2;
// Launch add() kernel on GPU.
add<<<1, 1>>>(a, b, c);
// Wait for GPU to finish before accessing on host.
HIP_CHECK(hipDeviceSynchronize());
// Print the result.
std::cout << *a << " + " << *b << " = " << *c << std::endl;
// Cleanup allocated memory.
delete[] c;
delete[] b;
delete[] a;
return 0;
}
// [sphinx-end]
@@ -0,0 +1,46 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "example_utils.hpp"
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
// [sphinx-start]
__global__ void kernel()
{
__shared__ int array[128];
__shared__ double result;
}
// [sphinx-end]
int main()
{
kernel<<<64, 512>>>();
HIP_CHECK(hipPeekAtLastError());
HIP_CHECK(hipDeviceSynchronize());
std::cout << "Success!" << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,65 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
// Declare a, b and c as static variables.
__managed__ int a, b, c;
int main()
{
// Setup input values.
a = 1;
b = 2;
// Launch add() kernel on GPU.
add<<<1, 1>>>(&a, &b, &c);
// Wait for GPU to finish before accessing on host.
HIP_CHECK(hipDeviceSynchronize());
// Print the result.
std::cout << a << " + " << b << " = " << c << std::endl;
return 0;
}
// [sphinx-end]
@@ -0,0 +1,85 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if (status != hipSuccess) \
{ \
std::cerr << "HIP error " << status \
<< ": " << hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
std::exit(EXIT_FAILURE); \
} \
}
// Kernel to perform some computation on allocated memory.
__global__ void myKernel(int* data, std::size_t numElements)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
if (tid < numElements)
{
data[tid] = tid * 2;
}
}
int main()
{
// Stream 0.
constexpr hipStream_t streamId = 0;
// Allocate memory with stream ordered semantics.
constexpr std::size_t numElements = 1024;
int* devData;
HIP_CHECK(hipMallocAsync(reinterpret_cast<void**>(&devData), numElements * sizeof(*devData), streamId));
// Launch the kernel to perform computation.
dim3 blockSize(256);
dim3 gridSize((numElements + blockSize.x - 1) / blockSize.x);
myKernel<<<gridSize, blockSize>>>(devData, numElements);
// Copy data back to host.
int* hostData = new int[numElements];
HIP_CHECK(hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost));
// Print the array.
for (std::size_t i = 0; i < numElements; ++i)
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
// Free memory with stream ordered semantics.
HIP_CHECK(hipFreeAsync(devData, streamId));
delete[] hostData;
// Synchronize to ensure completion.
HIP_CHECK(hipDeviceSynchronize());
return EXIT_SUCCESS;
}
// [sphinx-end]
@@ -20,16 +20,23 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "popcount.hpp"
#include <hip/hip_runtime.h>
#include <type_traits>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <random>
#include <vector>
#include <type_traits>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess){ \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
@@ -39,169 +46,185 @@
}
// [Sphinx template warp size block reduction kernel start]
template<uint32_t WarpSize>
using lane_mask_t = typename std::conditional<WarpSize == 32, uint32_t, uint64_t>::type;
template<std::uint32_t WarpSize>
using lane_mask_t = typename std::conditional<WarpSize == 32, std::uint32_t, std::uint64_t>::type;
template<uint32_t WarpSize>
__global__ void block_reduce(int* input, lane_mask_t<WarpSize>* mask, int* output, size_t size) {
extern __shared__ int shared[];
template<std::uint32_t WarpSize>
__global__ void block_reduce(int* input, lane_mask_t<WarpSize>* mask, int* output, size_t size)
{
extern __shared__ int shared[];
// Read of input with bounds check
auto read_global_safe = [&](const uint32_t i, const uint32_t lane_id, const uint32_t mask_id)
{
lane_mask_t<WarpSize> warp_mask = lane_mask_t<WarpSize>(1) << lane_id;
return (i < size) && (mask[mask_id] & warp_mask) ? input[i] : 0;
};
// Read of input with bounds check
auto read_global_safe = [&](const std::uint32_t i, const std::uint32_t lane_id, const std::uint32_t mask_id)
{
lane_mask_t<WarpSize> warp_mask = lane_mask_t<WarpSize>(1) << lane_id;
return (i < size) && (mask[mask_id] & warp_mask) ? input[i] : 0;
};
const uint32_t tid = threadIdx.x,
lid = threadIdx.x % WarpSize,
wid = threadIdx.x / WarpSize,
bid = blockIdx.x,
gid = bid * blockDim.x + tid;
const std::uint32_t tid = threadIdx.x,
lid = threadIdx.x % WarpSize,
wid = threadIdx.x / WarpSize,
bid = blockIdx.x,
gid = bid * blockDim.x + tid;
// Read input buffer to shared
shared[tid] = read_global_safe(gid, lid, bid * (blockDim.x / WarpSize) + wid);
__syncthreads();
// Shared reduction
for (uint32_t i = blockDim.x / 2; i >= WarpSize; i /= 2)
{
if (tid < i)
shared[tid] = shared[tid] + shared[tid + i];
// Read input buffer to shared
shared[tid] = read_global_safe(gid, lid, bid * (blockDim.x / WarpSize) + wid);
__syncthreads();
}
// Use local variable in warp reduction
int result = shared[tid];
__syncthreads();
// Shared reduction
for (std::uint32_t i = blockDim.x / 2; i >= WarpSize; i /= 2)
{
if (tid < i)
shared[tid] = shared[tid] + shared[tid + i];
__syncthreads();
}
// This loop would be unrolled the same with the runtime warpSize.
#pragma unroll
for (uint32_t i = WarpSize/2; i >= 1; i /= 2) {
result = result + __shfl_down(result, i);
}
// Use local variable in warp reduction
int result = shared[tid];
__syncthreads();
// Write result to output buffer
if (tid == 0)
output[bid] = result;
};
// This loop would be unrolled the same with the runtime warpSize.
#pragma unroll
for (std::uint32_t i = WarpSize/2; i >= 1; i /= 2)
{
result = result + __shfl_down(result, i);
}
// Write result to output buffer
if (tid == 0)
output[bid] = result;
}
// [Sphinx template warp size block reduction kernel end]
// [Sphinx template warp size mask generation start]
template<uint32_t WarpSize>
template<std::uint32_t WarpSize>
void generate_and_copy_mask(
void *d_mask,
std::vector<int>& vectorExpected,
int numOfBlocks,
int numberOfWarp,
int mask_size,
int mask_element_size) {
std::random_device rd;
std::mt19937_64 eng(rd());
void *d_mask,
std::vector<int>& vectorExpected,
int numOfBlocks,
int numberOfWarp,
int mask_size,
int mask_element_size)
{
std::random_device rd;
std::mt19937_64 eng(rd());
// Host side mask vector
std::vector<lane_mask_t<WarpSize>> mask(mask_size);
// Define uniform unsigned int distribution
std::uniform_int_distribution<lane_mask_t<WarpSize>> distr;
// Fill up the mask
for(int i=0; i < numOfBlocks; i++) {
int count = 0;
for(int j=0; j < numberOfWarp; j++) {
int mask_index = i * numberOfWarp + j;
mask[mask_index] = distr(eng);
if constexpr(WarpSize == 32)
count += __builtin_popcount(mask[mask_index]);
else
count += __builtin_popcountll(mask[mask_index]);
// Host side mask vector
std::vector<lane_mask_t<WarpSize>> mask(mask_size);
// Define uniform unsigned int distribution
std::uniform_int_distribution<lane_mask_t<WarpSize>> distr;
// Fill up the mask
for(int i=0; i < numOfBlocks; i++)
{
int count = 0;
for(int j=0; j < numberOfWarp; j++)
{
int mask_index = i * numberOfWarp + j;
mask[mask_index] = distr(eng);
if constexpr(WarpSize == 32)
count += popcount(static_cast<std::uint32_t>(mask[mask_index]));
else
count += popcount(mask[mask_index]);
}
vectorExpected[i]= count;
}
vectorExpected[i]= count;
}
// Copy the mask array
HIP_CHECK(hipMemcpy(d_mask, mask.data(), mask_size * mask_element_size, hipMemcpyHostToDevice));
// Copy the mask array
HIP_CHECK(hipMemcpy(d_mask, mask.data(), mask_size * mask_element_size, hipMemcpyHostToDevice));
}
// [Sphinx template warp size mask generation end]
int main() {
int main()
{
int deviceId = 0;
int warpSizeHost;
HIP_CHECK(hipDeviceGetAttribute(&warpSizeHost, hipDeviceAttributeWarpSize, deviceId));
std::cout << "Warp size: " << warpSizeHost << std::endl;
int deviceId = 0;
int warpSizeHost;
HIP_CHECK(hipDeviceGetAttribute(&warpSizeHost, hipDeviceAttributeWarpSize, deviceId));
std::cout << "Warp size: " << warpSizeHost << std::endl;
constexpr int numOfBlocks = 16;
constexpr int threadsPerBlock = 1024;
const int numberOfWarp = threadsPerBlock / warpSizeHost;
const int mask_element_size = warpSizeHost == 32 ? sizeof(std::uint32_t) : sizeof(std::uint64_t);
const int mask_size = numOfBlocks * numberOfWarp;
constexpr std::size_t arraySize = numOfBlocks * threadsPerBlock;
constexpr int numOfBlocks = 16;
constexpr int threadsPerBlock = 1024;
const int numberOfWarp = threadsPerBlock / warpSizeHost;
const int mask_element_size = warpSizeHost == 32 ? sizeof(uint32_t) : sizeof(uint64_t);
const int mask_size = numOfBlocks * numberOfWarp;
constexpr size_t arraySize = numOfBlocks * threadsPerBlock;
int *d_data, *d_results;
void *d_mask;
int initValue = 1;
std::vector<int> vectorInput(arraySize, initValue);
std::vector<int> vectorOutput(numOfBlocks);
std::vector<int> vectorExpected(numOfBlocks);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_data, arraySize * sizeof(*d_data)));
HIP_CHECK(hipMalloc(&d_mask, mask_size * mask_element_size));
HIP_CHECK(hipMalloc(&d_results, numOfBlocks * sizeof(*d_results)));
// Host to Device copy of the input array
HIP_CHECK(hipMemcpy(d_data, vectorInput.data(), arraySize * sizeof(*d_data), hipMemcpyHostToDevice));
int *d_data, *d_results;
void *d_mask;
int initValue = 1;
std::vector<int> vectorInput(arraySize, initValue);
std::vector<int> vectorOutput(numOfBlocks);
std::vector<int> vectorExpected(numOfBlocks);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_data, arraySize * sizeof(*d_data)));
HIP_CHECK(hipMalloc(&d_mask, mask_size * mask_element_size));
HIP_CHECK(hipMalloc(&d_results, numOfBlocks * sizeof(*d_results)));
// Host to Device copy of the input array
HIP_CHECK(hipMemcpy(d_data, vectorInput.data(), arraySize * sizeof(*d_data), hipMemcpyHostToDevice));
// [Sphinx template warp size select kernel start]
// Fill up the mask variable, copy to device and select the right kernel.
if(warpSizeHost == 32) {
// Generate and copy mask arrays
generate_and_copy_mask<32>(d_mask, vectorExpected, numOfBlocks, numberOfWarp, mask_size, mask_element_size);
// [Sphinx template warp size select kernel start]
// Fill up the mask variable, copy to device and select the right kernel.
if(warpSizeHost == 32)
{
// Generate and copy mask arrays
generate_and_copy_mask<32>(d_mask, vectorExpected, numOfBlocks, numberOfWarp, mask_size, mask_element_size);
// Start the kernel
block_reduce<32><<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
static_cast<uint32_t*>(d_mask),
d_results,
arraySize);
} else if(warpSizeHost == 64) {
// Generate and copy mask arrays
generate_and_copy_mask<64>(d_mask, vectorExpected, numOfBlocks, numberOfWarp, mask_size, mask_element_size);
// Start the kernel
block_reduce<64><<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
static_cast<uint64_t*>(d_mask),
d_results,
arraySize);
} else {
std::cerr << "Unsupported warp size." << std::endl;
return 0;
}
// [Sphinx template warp size select kernel end]
// Check the kernel launch
HIP_CHECK(hipGetLastError());
// Check for kernel execution error
HIP_CHECK(hipDeviceSynchronize());
// Device to Host copy of the result
HIP_CHECK(hipMemcpy(vectorOutput.data(), d_results, numOfBlocks * sizeof(*d_results), hipMemcpyDeviceToHost));
// Verify results
bool passed = true;
for(size_t i = 0; i < numOfBlocks; ++i) {
if(vectorOutput[i] != vectorExpected[i]) {
passed = false;
std::cerr << "Validation failed! Expected " << vectorExpected[i] << " got " << vectorOutput[i] << " at index: " << i << std::endl;
// Start the kernel
block_reduce<32><<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
static_cast<std::uint32_t*>(d_mask),
d_results,
arraySize);
}
}
if(passed){
std::cout << "Execution completed successfully." << std::endl;
}else{
std::cerr << "Execution failed." << std::endl;
}
else if(warpSizeHost == 64)
{
// Generate and copy mask arrays
generate_and_copy_mask<64>(d_mask, vectorExpected, numOfBlocks, numberOfWarp, mask_size, mask_element_size);
// Cleanup
HIP_CHECK(hipFree(d_data));
HIP_CHECK(hipFree(d_mask));
HIP_CHECK(hipFree(d_results));
return 0;
}
// Start the kernel
block_reduce<64><<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
static_cast<std::uint64_t*>(d_mask),
d_results,
arraySize);
}
else
{
std::cerr << "Unsupported warp size." << std::endl;
return EXIT_FAILURE;
}
// [Sphinx template warp size select kernel end]
// Check the kernel launch
HIP_CHECK(hipGetLastError());
// Check for kernel execution error
HIP_CHECK(hipDeviceSynchronize());
// Device to Host copy of the result
HIP_CHECK(hipMemcpy(vectorOutput.data(), d_results, numOfBlocks * sizeof(*d_results), hipMemcpyDeviceToHost));
// Verify results
bool passed = true;
for(std::size_t i = 0; i < numOfBlocks; ++i)
{
if(vectorOutput[i] != vectorExpected[i])
{
passed = false;
std::cerr << "Validation failed! Expected " << vectorExpected[i]
<< " got " << vectorOutput[i] << " at index: " << i << std::endl;
}
}
if(passed)
{
std::cout << "Execution completed successfully." << std::endl;
}
else
{
std::cerr << "Execution failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipFree(d_data));
HIP_CHECK(hipFree(d_mask));
HIP_CHECK(hipFree(d_results));
return EXIT_SUCCESS;
}
@@ -0,0 +1,66 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <hip/hip_runtime.h>
#include <cstdlib>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
<< " at " << __FILE__ << ":" \
<< __LINE__ << std::endl; \
} \
}
// [sphinx-kernel-start]
__global__ void kernel()
{
long long int start = clock64();
// kernel code
long long int stop = clock64();
long long int cycles = stop - start;
}
// [sphinx-kernel-end]
int main()
{
int deviceId = 0;
// [sphinx-query-start]
int wallClkRate = 0; //in kilohertz
HIP_CHECK(hipDeviceGetAttribute(&wallClkRate, hipDeviceAttributeWallClockRate, deviceId));
// [sphinx-query-end]
kernel<<<dim3{1, 1, 1}, dim3{32,1,1}>>>();
HIP_CHECK(hipDeviceSynchronize());
std::cout << "Device's wall clock rate is " << wallClkRate << " kHz." << std::endl;
return EXIT_SUCCESS;
}
@@ -0,0 +1,89 @@
// MIT License
//
// Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// [sphinx-start]
#include <hip/hip_runtime.h>
#include <iostream>
#define HIP_CHECK(expression) \
{ \
const hipError_t err = expression; \
if(err != hipSuccess) \
{ \
std::cerr << "HIP error: " \
<< hipGetErrorString(err) \
<< " at " << __LINE__ << "\n"; \
} \
}
// Addition of two values.
__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main()
{
int deviceId;
HIP_CHECK(hipGetDevice(&deviceId));
int *a, *b, *c;
// Allocate memory for a, b, and c accessible to both device and host codes.
HIP_CHECK(hipMallocManaged(&a, sizeof(*a)));
HIP_CHECK(hipMallocManaged(&b, sizeof(*b)));
HIP_CHECK(hipMallocManaged(&c, sizeof(*c)));
// Set memory advice for a and b to be read, located on and accessed by the GPU.
HIP_CHECK(hipMemAdvise(a, sizeof(*a), hipMemAdviseSetPreferredLocation, deviceId));
HIP_CHECK(hipMemAdvise(a, sizeof(*a), hipMemAdviseSetAccessedBy, deviceId));
HIP_CHECK(hipMemAdvise(a, sizeof(*a), hipMemAdviseSetReadMostly, deviceId));
HIP_CHECK(hipMemAdvise(b, sizeof(*b), hipMemAdviseSetPreferredLocation, deviceId));
HIP_CHECK(hipMemAdvise(b, sizeof(*b), hipMemAdviseSetAccessedBy, deviceId));
HIP_CHECK(hipMemAdvise(b, sizeof(*b), hipMemAdviseSetReadMostly, deviceId));
// Set memory advice for c to be read, located on and accessed by the CPU.
HIP_CHECK(hipMemAdvise(c, sizeof(*c), hipMemAdviseSetPreferredLocation, hipCpuDeviceId));
HIP_CHECK(hipMemAdvise(c, sizeof(*c), hipMemAdviseSetAccessedBy, hipCpuDeviceId));
HIP_CHECK(hipMemAdvise(c, sizeof(*c), hipMemAdviseSetReadMostly, hipCpuDeviceId));
// Setup input values.
*a = 1;
*b = 2;
// Launch add() kernel on GPU.
add<<<1, 1>>>(a, b, c);
// Wait for GPU to finish before accessing on host.
HIP_CHECK(hipDeviceSynchronize());
// Prints the result.
std::cout << *a << " + " << *b << " = " << *c << std::endl;
// Cleanup allocated memory.
HIP_CHECK(hipFree(a));
HIP_CHECK(hipFree(b));
HIP_CHECK(hipFree(c));
return 0;
}
// [sphinx-end]
@@ -20,16 +20,23 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "popcount.hpp"
#include <hip/hip_runtime.h>
#include <type_traits>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <random>
#include <vector>
#include <type_traits>
#define HIP_CHECK(expression) \
{ \
const hipError_t status = expression; \
if(status != hipSuccess){ \
if(status != hipSuccess) \
{ \
std::cerr << "HIP error " \
<< status << ": " \
<< hipGetErrorString(status) \
@@ -39,146 +46,164 @@
}
// [Sphinx HIP warp size block reduction kernel start]
__global__ void block_reduce(int* input, uint64_t* mask, int* output, size_t size){
extern __shared__ int shared[];
// Read of input with bounds check
auto read_global_safe = [&](const uint32_t i, const uint32_t lane_id, const uint32_t mask_id)
{
uint64_t warp_mask = 1ull << lane_id;
return (i < size) && (mask[mask_id] & warp_mask) ? input[i] : 0;
};
const uint32_t tid = threadIdx.x,
lid = threadIdx.x % warpSize,
wid = threadIdx.x / warpSize,
bid = blockIdx.x,
gid = bid * blockDim.x + tid;
// Read input buffer to shared
shared[tid] = read_global_safe(gid, lid, bid * (blockDim.x / warpSize) + wid);
__syncthreads();
// Shared reduction
for (uint32_t i = blockDim.x / 2; i >= warpSize; i /= 2)
{
if (tid < i)
shared[tid] = shared[tid] + shared[tid + i];
__global__ void block_reduce(int* input, std::uint64_t* mask, int* output, std::size_t size)
{
extern __shared__ int shared[];
// Read of input with bounds check
auto read_global_safe = [&](const std::uint32_t i, const std::uint32_t lane_id, const std::uint32_t mask_id)
{
std::uint64_t warp_mask = 1ull << lane_id;
return (i < size) && (mask[mask_id] & warp_mask) ? input[i] : 0;
};
const std::uint32_t tid = threadIdx.x,
lid = threadIdx.x % warpSize,
wid = threadIdx.x / warpSize,
bid = blockIdx.x,
gid = bid * blockDim.x + tid;
// Read input buffer to shared
shared[tid] = read_global_safe(gid, lid, bid * (blockDim.x / warpSize) + wid);
__syncthreads();
}
// Use local variable in warp reduction
int result = shared[tid];
__syncthreads();
// Shared reduction
for (std::uint32_t i = blockDim.x / 2; i >= warpSize; i /= 2)
{
if (tid < i)
shared[tid] = shared[tid] + shared[tid + i];
__syncthreads();
}
// This loop would be unrolled the same with the compile-time WarpSize.
#pragma unroll
for (uint32_t i = warpSize/2; i >= 1; i /= 2) {
result = result + __shfl_down(result, i);
}
// Use local variable in warp reduction
int result = shared[tid];
__syncthreads();
// Write result to output buffer
if (tid == 0)
output[bid] = result;
};
// This loop would be unrolled the same with the compile-time WarpSize.
#pragma unroll
for (std::uint32_t i = warpSize/2; i >= 1; i /= 2) {
result = result + __shfl_down(result, i);
}
// Write result to output buffer
if (tid == 0)
output[bid] = result;
}
// [Sphinx HIP warp size block reduction kernel end]
// [Sphinx HIP warp size mask generation start]
void generate_and_copy_mask(
uint64_t *d_mask,
std::vector<int>& vectorExpected,
int warpSizeHost,
int numOfBlocks,
int numberOfWarp,
int mask_size,
int mask_element_size) {
std::random_device rd;
std::mt19937_64 eng(rd());
std::uint64_t *d_mask,
std::vector<int>& vectorExpected,
int warpSizeHost,
int numOfBlocks,
int numberOfWarp,
int mask_size,
int mask_element_size)
{
std::random_device rd;
std::mt19937_64 eng(rd());
// Host side mask vector
std::vector<uint64_t> mask(mask_size);
// Define uniform unsigned int distribution
std::uniform_int_distribution<uint64_t> distr;
// Fill up the mask
for(int i=0; i < numOfBlocks; i++) {
int count = 0;
for(int j=0; j < numberOfWarp; j++) {
int mask_index = i * numberOfWarp + j;
mask[mask_index] = distr(eng);
if(warpSizeHost == 32)
count += __builtin_popcount(mask[mask_index]);
else
count += __builtin_popcountll(mask[mask_index]);
// Host side mask vector
std::vector<std::uint64_t> mask(mask_size);
// Define uniform unsigned int distribution
std::uniform_int_distribution<std::uint64_t> distr;
// Fill up the mask
for(int i=0; i < numOfBlocks; i++)
{
int count = 0;
for(int j=0; j < numberOfWarp; j++)
{
int mask_index = i * numberOfWarp + j;
mask[mask_index] = distr(eng);
if(warpSizeHost == 32)
count += popcount(static_cast<std::uint32_t>(mask[mask_index]));
else
count += popcount(mask[mask_index]);
}
vectorExpected[i]= count;
}
vectorExpected[i]= count;
}
// Copy the mask array
HIP_CHECK(hipMemcpy(d_mask, mask.data(), mask_size * mask_element_size, hipMemcpyHostToDevice));
// Copy the mask array
HIP_CHECK(hipMemcpy(d_mask, mask.data(), mask_size * mask_element_size, hipMemcpyHostToDevice));
}
// [Sphinx HIP warp size mask generation end]
int main() {
int deviceId = 0;
int warpSizeHost;
HIP_CHECK(hipDeviceGetAttribute(&warpSizeHost, hipDeviceAttributeWarpSize, deviceId));
std::cout << "Warp size: " << warpSizeHost << std::endl;
constexpr int numOfBlocks = 16;
constexpr int threadsPerBlock = 1024;
const int numberOfWarp = threadsPerBlock / warpSizeHost;
const int mask_element_size = sizeof(uint64_t);
const int mask_size = numOfBlocks * numberOfWarp;
constexpr size_t arraySize = numOfBlocks * threadsPerBlock;
int *d_data, *d_results;
uint64_t *d_mask;
int initValue = 1;
std::vector<int> vectorInput(arraySize, initValue);
std::vector<int> vectorOutput(numOfBlocks);
std::vector<int> vectorExpected(numOfBlocks);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_data, arraySize * sizeof(*d_data)));
HIP_CHECK(hipMalloc(&d_mask, mask_size * mask_element_size));
HIP_CHECK(hipMalloc(&d_results, numOfBlocks * sizeof(*d_results)));
// Host to Device copy of the input array
HIP_CHECK(hipMemcpy(d_data, vectorInput.data(), arraySize * sizeof(*d_data), hipMemcpyHostToDevice));
int main()
{
int deviceId = 0;
int warpSizeHost;
HIP_CHECK(hipDeviceGetAttribute(&warpSizeHost, hipDeviceAttributeWarpSize, deviceId));
std::cout << "Warp size: " << warpSizeHost << std::endl;
constexpr int numOfBlocks = 16;
constexpr int threadsPerBlock = 1024;
const int numberOfWarp = threadsPerBlock / warpSizeHost;
const int mask_element_size = sizeof(std::uint64_t);
const int mask_size = numOfBlocks * numberOfWarp;
constexpr std::size_t arraySize = numOfBlocks * threadsPerBlock;
int *d_data, *d_results;
std::uint64_t *d_mask;
int initValue = 1;
std::vector<int> vectorInput(arraySize, initValue);
std::vector<int> vectorOutput(numOfBlocks);
std::vector<int> vectorExpected(numOfBlocks);
// Allocate device memory
HIP_CHECK(hipMalloc(&d_data, arraySize * sizeof(*d_data)));
HIP_CHECK(hipMalloc(&d_mask, mask_size * mask_element_size));
HIP_CHECK(hipMalloc(&d_results, numOfBlocks * sizeof(*d_results)));
// Host to Device copy of the input array
HIP_CHECK(hipMemcpy(d_data, vectorInput.data(), arraySize * sizeof(*d_data), hipMemcpyHostToDevice));
// [Sphinx HIP warp size select kernel start]
// Generate and copy mask arrays
generate_and_copy_mask(
d_mask,
vectorExpected,
warpSizeHost,
numOfBlocks,
numberOfWarp,
mask_size,
mask_element_size);
// [Sphinx HIP warp size select kernel start]
// Generate and copy mask arrays
generate_and_copy_mask(
d_mask,
vectorExpected,
warpSizeHost,
numOfBlocks,
numberOfWarp,
mask_size,
mask_element_size);
// Start the kernel
block_reduce<<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
d_mask,
d_results,
arraySize);
// [Sphinx HIP warp size select kernel end]
// Start the kernel
block_reduce<<<dim3(numOfBlocks), dim3(threadsPerBlock), threadsPerBlock * sizeof(*d_data)>>>(
d_data,
d_mask,
d_results,
arraySize);
// [Sphinx HIP warp size select kernel end]
// Check the kernel launch
HIP_CHECK(hipGetLastError());
// Check for kernel execution error
HIP_CHECK(hipDeviceSynchronize());
// Device to Host copy of the result
HIP_CHECK(hipMemcpy(vectorOutput.data(), d_results, numOfBlocks * sizeof(*d_results), hipMemcpyDeviceToHost));
// Verify results
bool passed = true;
for(size_t i = 0; i < numOfBlocks; ++i) {
if(vectorOutput[i] != vectorExpected[i]) {
passed = false;
std::cerr << "Validation failed! Expected " << vectorExpected[i] << " got " << vectorOutput[i] << " at index: " << i << std::endl;
// Check the kernel launch
HIP_CHECK(hipGetLastError());
// Check for kernel execution error
HIP_CHECK(hipDeviceSynchronize());
// Device to Host copy of the result
HIP_CHECK(hipMemcpy(vectorOutput.data(), d_results, numOfBlocks * sizeof(*d_results), hipMemcpyDeviceToHost));
// Verify results
bool passed = true;
for(std::size_t i = 0; i < numOfBlocks; ++i)
{
if(vectorOutput[i] != vectorExpected[i])
{
passed = false;
std::cerr << "Validation failed! Expected " << vectorExpected[i]
<< " got " << vectorOutput[i] << " at index: " << i << std::endl;
}
}
}
if(passed){
std::cout << "Execution completed successfully." << std::endl;
}else{
std::cerr << "Execution failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipFree(d_data));
HIP_CHECK(hipFree(d_mask));
HIP_CHECK(hipFree(d_results));
return 0;
}
if(passed)
{
std::cout << "Execution completed successfully." << std::endl;
}
else
{
std::cerr << "Execution failed." << std::endl;
}
// Cleanup
HIP_CHECK(hipFree(d_data));
HIP_CHECK(hipFree(d_mask));
HIP_CHECK(hipFree(d_results));
return EXIT_SUCCESS;
}