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:
@@ -207,319 +207,24 @@ The example codes
|
||||
|
||||
.. tab-item:: Sequential
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
// GPU Kernels
|
||||
__global__ void kernelA(double* arrayA, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayA[x] += 1.0;}
|
||||
};
|
||||
__global__ void kernelB(double* arrayA, double* arrayB, size_t size){
|
||||
const 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 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
|
||||
hipLaunchKernelGGL(kernelA, dim3(numOfBlocks), dim3(threadsPerBlock), 0, 0, d_dataA, arraySize);
|
||||
hipLaunchKernelGGL(kernelB, dim3(numOfBlocks), dim3(threadsPerBlock), 0, 0, 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(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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/sequential_kernel_execution.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: Asynchronous
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
// GPU Kernels
|
||||
__global__ void kernelA(double* arrayA, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayA[x] += 1.0;}
|
||||
};
|
||||
__global__ void kernelB(double* arrayA, double* arrayB, size_t size){
|
||||
const 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 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
|
||||
hipLaunchKernelGGL(kernelA, dim3(numOfBlocks), dim3(threadsPerBlock), 0, streamA, d_dataA, arraySize);
|
||||
// Wait for streamA finish
|
||||
HIP_CHECK(hipStreamSynchronize(streamA));
|
||||
// Stream 2: Kernel 2
|
||||
hipLaunchKernelGGL(kernelB, dim3(numOfBlocks), dim3(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(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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/async_kernel_execution.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: hipStreamWaitEvent
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
// GPU Kernels
|
||||
__global__ void kernelA(double* arrayA, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayA[x] += 1.0;}
|
||||
};
|
||||
__global__ void kernelB(double* arrayA, double* arrayB, size_t size){
|
||||
const 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 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
|
||||
hipLaunchKernelGGL(kernelA, dim3(numOfBlocks), dim3(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
|
||||
hipLaunchKernelGGL(kernelB, dim3(numOfBlocks), dim3(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(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;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/event_based_synchronization.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
HIP Graphs
|
||||
===============================================================================
|
||||
|
||||
@@ -33,38 +33,10 @@ You can adjust the call stack size as shown in the following example, allowing
|
||||
fine-tuning based on specific kernel requirements. This helps prevent stack
|
||||
overflow errors by ensuring sufficient stack memory is allocated.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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()
|
||||
{
|
||||
size_t stackSize;
|
||||
HIP_CHECK(hipDeviceGetLimit(&stackSize, hipLimitStackSize));
|
||||
std::cout << "Default stack size: " << stackSize << " bytes" << std::endl;
|
||||
|
||||
// Set a new stack size
|
||||
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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/call_stack_management.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Depending on the GPU model, at full occupancy, it can consume a significant
|
||||
amount of memory. For instance, an MI300X with 304 compute units (CU) and up to
|
||||
@@ -81,49 +53,7 @@ needed for the call stack due to the GPUs inherent parallelism. This can be
|
||||
achieved by increasing stack size or optimizing code to reduce stack usage. To
|
||||
detect stack overflow add proper error handling or use debugging tools.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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 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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/device_recursion.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
@@ -68,70 +68,7 @@ Complete example
|
||||
A complete example to demonstrate the error handling with a simple addition of
|
||||
two values kernel:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
// Addition of two values.
|
||||
__global__ void add(int *a, int *b, int *c, size_t size) {
|
||||
const 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 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(*d_a)));
|
||||
HIP_CHECK(hipMalloc(&d_b, arraySize * sizeof(*d_b)));
|
||||
HIP_CHECK(hipMalloc(&d_c, arraySize * sizeof(*d_c)));
|
||||
|
||||
// Copy input values to device.
|
||||
HIP_CHECK(hipMemcpy(d_a, &a, arraySize * sizeof(*d_a), hipMemcpyHostToDevice));
|
||||
HIP_CHECK(hipMemcpy(d_b, &b, arraySize * sizeof(*d_b), hipMemcpyHostToDevice));
|
||||
|
||||
// Launch add() kernel on GPU.
|
||||
hipLaunchKernelGGL(add, dim3(numOfBlocks), dim3(threadsPerBlock), 0, 0, 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, d_c, arraySize * sizeof(*d_c), 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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/error_handling.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
@@ -14,6 +14,10 @@ method via streams. A HIP graph is made up of nodes and edges. The nodes of a
|
||||
HIP graph represent the operations performed, while the edges mark dependencies
|
||||
between those operations.
|
||||
|
||||
.. hint::
|
||||
The :ref:`HIP Graph API tutorial <hip_graph_api_tutorial>` demonstrates how
|
||||
to use HIP graphs in a real-world application.
|
||||
|
||||
The nodes can be one of the following:
|
||||
|
||||
- empty nodes
|
||||
@@ -180,124 +184,10 @@ The general flow for using stream capture to create a graph template is:
|
||||
The following code is an example of how to use the HIP graph API to capture a
|
||||
graph from a stream.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
__global__ void kernelA(double* arrayA, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayA[x] *= 2.0;}
|
||||
};
|
||||
__global__ void kernelB(int* arrayB, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayB[x] = 3;}
|
||||
};
|
||||
__global__ void kernelC(double* arrayA, const int* arrayB, size_t size){
|
||||
const 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 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(&d_arrayA, arraySize*sizeof(double), captureStream));
|
||||
HIP_CHECK(hipMallocAsync(&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));
|
||||
|
||||
// Verify results
|
||||
constexpr double expected = initValue * 2.0 + 3;
|
||||
bool passed = true;
|
||||
for(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));
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/graph_capture.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Explicit graph creation
|
||||
================================================================================
|
||||
@@ -333,178 +223,7 @@ The general flow for explicitly creating a graph is usually:
|
||||
|
||||
The following code example demonstrates how to explicitly create nodes in order to create a graph.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <vector>
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
|
||||
__global__ void kernelA(double* arrayA, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayA[x] *= 2.0;}
|
||||
};
|
||||
__global__ void kernelB(int* arrayB, size_t size){
|
||||
const size_t x = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
if(x < size){arrayB[x] = 3;}
|
||||
};
|
||||
__global__ void kernelC(double* arrayA, const int* arrayB, size_t size){
|
||||
const 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;
|
||||
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, 1, 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, 1, &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));
|
||||
|
||||
// Verify results
|
||||
constexpr double expected = initValue * 2.0 + 3;
|
||||
bool passed = true;
|
||||
for(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));
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/graph_creation.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
@@ -66,24 +66,10 @@ which can be used to loop over the available GPUs.
|
||||
|
||||
Example code of querying GPUs:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#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;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/simple_device_query.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Setting the GPU
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -47,61 +47,10 @@ C++ application.
|
||||
|
||||
**Example:** Using pageable host memory in HIP
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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;
|
||||
}
|
||||
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));
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/pageable_host_memory.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -133,61 +82,10 @@ processes, which can negatively impact the overall performance of the host.
|
||||
|
||||
**Example:** Using pinned memory in HIP
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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((int **)&host_input, element_number * sizeof(int)));
|
||||
HIP_CHECK(hipHostMalloc((int **)&host_output, element_number * sizeof(int)));
|
||||
|
||||
// Host data preparation
|
||||
for (int i = 0; i < element_number; i++) {
|
||||
host_input[i] = i;
|
||||
}
|
||||
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));
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/pinned_host_memory.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. _memory_allocation_flags:
|
||||
|
||||
|
||||
+29
-272
@@ -37,102 +37,17 @@ Here is how to use stream ordered memory allocation:
|
||||
.. tab-set::
|
||||
.. tab-item:: Stream Ordered Memory Allocation
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <iostream>
|
||||
#include <hip/hip_runtime.h>
|
||||
|
||||
// Kernel to perform some computation on allocated memory.
|
||||
__global__ void myKernel(int* data, size_t numElements) {
|
||||
int tid = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
if (tid < numElements) {
|
||||
data[tid] = tid * 2;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize HIP.
|
||||
hipInit(0);
|
||||
|
||||
// Stream 0.
|
||||
constexpr hipStream_t streamId = 0;
|
||||
|
||||
// Allocate memory with stream ordered semantics.
|
||||
constexpr size_t numElements = 1024;
|
||||
int* devData;
|
||||
hipMallocAsync(&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];
|
||||
hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost);
|
||||
|
||||
// Print the array.
|
||||
for (size_t i = 0; i < numElements; ++i) {
|
||||
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
|
||||
}
|
||||
|
||||
// Free memory with stream ordered semantics.
|
||||
hipFreeAsync(devData, streamId);
|
||||
delete[] hostData;
|
||||
|
||||
// Synchronize to ensure completion.
|
||||
hipDeviceSynchronize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/stream_ordered_memory_allocation.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: Ordinary Allocation
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <iostream>
|
||||
#include <hip/hip_runtime.h>
|
||||
|
||||
// Kernel to perform some computation on allocated memory.
|
||||
__global__ void myKernel(int* data, size_t numElements) {
|
||||
int tid = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
if (tid < numElements) {
|
||||
data[tid] = tid * 2;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize HIP.
|
||||
hipInit(0);
|
||||
|
||||
// Allocate memory.
|
||||
constexpr size_t numElements = 1024;
|
||||
int* devData;
|
||||
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];
|
||||
hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost);
|
||||
|
||||
// Print the array.
|
||||
for (size_t i = 0; i < numElements; ++i) {
|
||||
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
|
||||
}
|
||||
|
||||
// Free memory.
|
||||
hipFree(devData);
|
||||
delete[] hostData;
|
||||
|
||||
// Synchronize to ensure completion.
|
||||
hipDeviceSynchronize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/ordinary_memory_allocation.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
For more details, see :ref:`stream_ordered_memory_allocator_reference`.
|
||||
|
||||
@@ -148,121 +63,29 @@ The ``hipMallocAsync()`` function uses the current memory pool and also provides
|
||||
|
||||
Unlike NVIDIA CUDA, where stream-ordered memory allocation can be implicit, ROCm HIP is explicit. This requires managing memory allocation for each stream in HIP while ensuring precise control over memory usage and synchronization.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <iostream>
|
||||
#include <hip/hip_runtime.h>
|
||||
|
||||
// Kernel to perform some computation on allocated memory.
|
||||
__global__ void myKernel(int* data, 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;
|
||||
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;
|
||||
hipMemPoolCreate(&memPool, &poolProps);
|
||||
|
||||
// Allocate memory from the pool asynchronously.
|
||||
constexpr size_t numElements = 1024;
|
||||
int* devData = nullptr;
|
||||
hipMallocFromPoolAsync(&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.
|
||||
hipStreamSynchronize(stream);
|
||||
|
||||
// Copy data back to host.
|
||||
int* hostData = new int[numElements];
|
||||
hipMemcpy(hostData, devData, numElements * sizeof(*devData), hipMemcpyDeviceToHost);
|
||||
|
||||
// Print the array.
|
||||
for (size_t i = 0; i < numElements; ++i) {
|
||||
std::cout << "Element " << i << ": " << hostData[i] << std::endl;
|
||||
}
|
||||
|
||||
// Free the allocated memory.
|
||||
hipFreeAsync(devData, stream);
|
||||
|
||||
// Synchronize the stream again to ensure all operations are complete.
|
||||
hipStreamSynchronize(stream);
|
||||
|
||||
// Destroy the memory pool and stream.
|
||||
hipMemPoolDestroy(memPool);
|
||||
hipStreamDestroy(stream);
|
||||
|
||||
// Free host memory.
|
||||
delete[] hostData;
|
||||
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/memory_pool.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Trim pools
|
||||
----------
|
||||
|
||||
The memory allocator allows you to allocate and free memory in stream order. To control memory usage, set the release threshold attribute using ``hipMemPoolAttrReleaseThreshold``. This threshold specifies the amount of reserved memory in bytes to hold onto.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
uint64_t threshold = UINT64_MAX;
|
||||
hipMemPoolSetAttribute(memPool, hipMemPoolAttrReleaseThreshold, &threshold);
|
||||
.. literalinclude:: ../../../tools/example_codes/memory_pool_threshold.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
When the amount of memory held in the memory pool exceeds the threshold, the allocator tries to release memory back to the operating system during the next call to stream, event, or context synchronization.
|
||||
|
||||
To improve performance, it is a good practice to adjust the memory pool size using ``hipMemPoolTrimTo()``. It helps to reclaim memory from an excessive memory pool, which optimizes memory usage for your application.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
hipMemPool_t memPool;
|
||||
hipDevice_t device = 0; // Specify the device index.
|
||||
|
||||
// Initialize the device.
|
||||
hipSetDevice(device);
|
||||
|
||||
// Get the default memory pool for the device.
|
||||
hipDeviceGetDefaultMemPool(&memPool, device);
|
||||
|
||||
// Allocate memory from the pool (e.g., 1 MB).
|
||||
size_t allocSize = 1 * 1024 * 1024;
|
||||
void* ptr;
|
||||
hipMalloc(&ptr, allocSize);
|
||||
|
||||
// Free the allocated memory.
|
||||
hipFree(ptr);
|
||||
|
||||
// Trim the memory pool to a specific size (e.g., 512 KB).
|
||||
size_t newSize = 512 * 1024;
|
||||
hipMemPoolTrimTo(memPool, newSize);
|
||||
|
||||
// Clean up.
|
||||
hipMemPoolDestroy(memPool);
|
||||
|
||||
std::cout << "Memory pool trimmed to " << newSize << " bytes." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/memory_pool_trim.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Resource usage statistics
|
||||
-------------------------
|
||||
@@ -276,81 +99,10 @@ Resource usage statistics help in optimization. Here is the list of pool attribu
|
||||
|
||||
To reset these attributes to the current value, use ``hipMemPoolSetAttribute()``.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <iostream>
|
||||
#include <hip/hip_runtime.h>
|
||||
|
||||
// Sample helper functions for getting the usage statistics in bulk.
|
||||
struct usageStatistics {
|
||||
uint64_t reservedMemCurrent;
|
||||
uint64_t reservedMemHigh;
|
||||
uint64_t usedMemCurrent;
|
||||
uint64_t usedMemHigh;
|
||||
};
|
||||
|
||||
void getUsageStatistics(hipMemPool_t memPool, struct usageStatistics *statistics) {
|
||||
hipMemPoolGetAttribute(memPool, hipMemPoolAttrReservedMemCurrent, &statistics->reservedMemCurrent);
|
||||
hipMemPoolGetAttribute(memPool, hipMemPoolAttrReservedMemHigh, &statistics->reservedMemHigh);
|
||||
hipMemPoolGetAttribute(memPool, hipMemPoolAttrUsedMemCurrent, &statistics->usedMemCurrent);
|
||||
hipMemPoolGetAttribute(memPool, hipMemPoolAttrUsedMemHigh, &statistics->usedMemHigh);
|
||||
}
|
||||
|
||||
// Resetting the watermarks resets them to the current value.
|
||||
void resetStatistics(hipMemPool_t memPool) {
|
||||
uint64_t value = 0;
|
||||
hipMemPoolSetAttribute(memPool, hipMemPoolAttrReservedMemHigh, &value);
|
||||
hipMemPoolSetAttribute(memPool, hipMemPoolAttrUsedMemHigh, &value);
|
||||
}
|
||||
|
||||
int main() {
|
||||
hipMemPool_t memPool;
|
||||
hipDevice_t device = 0; // Specify the device index.
|
||||
|
||||
// Initialize the device.
|
||||
hipSetDevice(device);
|
||||
|
||||
// Get the default memory pool for the device.
|
||||
hipDeviceGetDefaultMemPool(&memPool, device);
|
||||
|
||||
// Allocate memory from the pool (e.g., 1 MB).
|
||||
size_t allocSize = 1 * 1024 * 1024;
|
||||
void* ptr;
|
||||
hipMalloc(&ptr, allocSize);
|
||||
|
||||
// Free the allocated memory.
|
||||
hipFree(ptr);
|
||||
|
||||
// Trim the memory pool to a specific size (e.g., 512 KB).
|
||||
size_t newSize = 512 * 1024;
|
||||
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;
|
||||
|
||||
// Clean up.
|
||||
hipMemPoolDestroy(memPool);
|
||||
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../../tools/example_codes/memory_pool_resource_usage_statistics.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Memory reuse policies
|
||||
---------------------
|
||||
@@ -369,6 +121,11 @@ Allocations are initially accessible from the device where they reside.
|
||||
Interprocess memory handling
|
||||
=============================
|
||||
|
||||
.. attention::
|
||||
IPC API calls are only supported on systems with an active ``amdgpu-dkms`` driver. Please refer to the
|
||||
`AMDGPU documentation <https://instinct.docs.amd.com/projects/amdgpu-docs/en/latest/index.html>`__ for information
|
||||
on how to install ``amdgpu-dkms``.
|
||||
|
||||
Interprocess capable (IPC) memory pools facilitate efficient and secure sharing of GPU memory between processes.
|
||||
|
||||
To achieve interprocess memory sharing, you can use either :ref:`device pointer <device-pointer>` or :ref:`shareable handle <shareable-handle>`. Both provide allocator (export) and consumer (import) interfaces.
|
||||
|
||||
@@ -303,207 +303,35 @@ explicit memory management example is presented in the last tab.
|
||||
|
||||
.. tab-item:: hipMallocManaged()
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/dynamic_unified_memory.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 22-25
|
||||
|
||||
#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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: __managed__
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/static_unified_memory.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 19-20
|
||||
|
||||
#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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, &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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: new
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/standard_unified_memory.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 21-24
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <iostream>
|
||||
#include <new>
|
||||
|
||||
#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 with proper alignment for performance
|
||||
int *a = new(std::align_val_t(128)) int[1];
|
||||
int *b = new(std::align_val_t(128)) int[1];
|
||||
int *c = new(std::align_val_t(128)) int[1];
|
||||
|
||||
// Setup input values.
|
||||
*a = 1;
|
||||
*b = 2;
|
||||
|
||||
// Launch add() kernel on GPU.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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 with matching aligned delete.
|
||||
::operator delete[](a, std::align_val_t(128));
|
||||
::operator delete[](b, std::align_val_t(128));
|
||||
::operator delete[](c, std::align_val_t(128));
|
||||
|
||||
return 0;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: Explicit Memory Management
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/explicit_memory.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 27-34, 39-40
|
||||
|
||||
#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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
.. _using unified memory:
|
||||
|
||||
@@ -559,65 +387,11 @@ Data prefetching is a technique used to improve the performance of your
|
||||
application by moving data to the desired device before it's actually
|
||||
needed. ``hipCpuDeviceId`` is a special constant to specify the CPU as target.
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/data_prefetching.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 33-36,41-42
|
||||
|
||||
#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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
Memory advice
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -642,71 +416,11 @@ impact on performance can vary based on the specific use case and the system.
|
||||
The following is the updated version of the example above with memory advice
|
||||
instead of prefetching.
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/unified_memory_advice.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 29-41
|
||||
|
||||
#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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
Memory range attributes
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -714,70 +428,11 @@ Memory range attributes
|
||||
:cpp:func:`hipMemRangeGetAttribute()` allows you to query attributes of a given
|
||||
memory range. The attributes are given in :cpp:enum:`hipMemRangeAttribute`.
|
||||
|
||||
.. code-block:: cpp
|
||||
.. literalinclude:: ../../../tools/example_codes/memory_range_attributes.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 44-49
|
||||
|
||||
#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;
|
||||
unsigned int attributeValue;
|
||||
constexpr 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.
|
||||
hipLaunchKernelGGL(add, dim3(1), dim3(1), 0, 0, 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;
|
||||
}
|
||||
:language: cpp
|
||||
|
||||
Asynchronously attach memory to a stream
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -22,43 +22,10 @@ dynamic selections during runtime to ensure optimal performance.
|
||||
|
||||
If the application does not define a specific GPU, device 0 is selected.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
int deviceCount;
|
||||
hipGetDeviceCount(&deviceCount);
|
||||
std::cout << "Number of devices: " << deviceCount << std::endl;
|
||||
|
||||
for (int deviceId = 0; deviceId < deviceCount; ++deviceId)
|
||||
{
|
||||
hipDeviceProp_t deviceProp;
|
||||
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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/device_enumeration.cpp
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
.. _multi_device_selection:
|
||||
|
||||
@@ -72,71 +39,10 @@ different GPUs might have different capabilities or workloads. By selecting the
|
||||
appropriate device, you ensure that the computational tasks are directed to the
|
||||
correct GPU, optimizing performance and resource utilization.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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; \
|
||||
exit(status); \
|
||||
} \
|
||||
}
|
||||
|
||||
__global__ void simpleKernel(double *data)
|
||||
{
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
data[idx] = idx * 2.0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
double* deviceData0;
|
||||
double* deviceData1;
|
||||
size_t size = 1024 * sizeof(*deviceData0);
|
||||
|
||||
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<<<1000, 128>>>(deviceData0); // 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<<<1000, 128>>>(deviceData1); // Launch kernel on device 1
|
||||
HIP_CHECK(hipDeviceSynchronize());
|
||||
|
||||
// Copy result from device 0
|
||||
double hostData0[1024];
|
||||
HIP_CHECK(hipSetDevice(deviceId0));
|
||||
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
|
||||
|
||||
// Copy result from device 1
|
||||
double hostData1[1024];
|
||||
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 0;
|
||||
}
|
||||
|
||||
.. literalinclude:: ../../tools/example_codes/device_selection.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Stream and event behavior
|
||||
===============================================================================
|
||||
@@ -151,100 +57,10 @@ conditions and optimizes data flow in multi-GPU systems. Together, streams and
|
||||
events maximize performance by enabling parallel execution, load balancing, and
|
||||
effective resource utilization across heterogeneous hardware.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <iostream>
|
||||
|
||||
__global__ void simpleKernel(double *data)
|
||||
{
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
data[idx] = idx * 2.0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int numDevices;
|
||||
hipGetDeviceCount(&numDevices);
|
||||
|
||||
if (numDevices < 2) {
|
||||
std::cerr << "This example requires at least two GPUs." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
double *deviceData0, *deviceData1;
|
||||
size_t size = 1024 * sizeof(*deviceData0);
|
||||
|
||||
// Create streams and events for each device
|
||||
hipStream_t stream0, stream1;
|
||||
hipEvent_t startEvent0, stopEvent0, startEvent1, stopEvent1;
|
||||
|
||||
// Initialize device 0
|
||||
hipSetDevice(0);
|
||||
hipStreamCreate(&stream0);
|
||||
hipEventCreate(&startEvent0);
|
||||
hipEventCreate(&stopEvent0);
|
||||
hipMalloc(&deviceData0, size);
|
||||
|
||||
// Initialize device 1
|
||||
hipSetDevice(1);
|
||||
hipStreamCreate(&stream1);
|
||||
hipEventCreate(&startEvent1);
|
||||
hipEventCreate(&stopEvent1);
|
||||
hipMalloc(&deviceData1, size);
|
||||
|
||||
// Record the start event on device 0
|
||||
hipSetDevice(0);
|
||||
hipEventRecord(startEvent0, stream0);
|
||||
|
||||
// Launch the kernel asynchronously on device 0
|
||||
simpleKernel<<<1000, 128, 0, stream0>>>(deviceData0);
|
||||
|
||||
// Record the stop event on device 0
|
||||
hipEventRecord(stopEvent0, stream0);
|
||||
|
||||
// Wait for the stop event on device 0 to complete
|
||||
hipEventSynchronize(stopEvent0);
|
||||
|
||||
// Record the start event on device 1
|
||||
hipSetDevice(1);
|
||||
hipEventRecord(startEvent1, stream1);
|
||||
|
||||
// Launch the kernel asynchronously on device 1
|
||||
simpleKernel<<<1000, 128, 0, stream1>>>(deviceData1);
|
||||
|
||||
// Record the stop event on device 1
|
||||
hipEventRecord(stopEvent1, stream1);
|
||||
|
||||
// Wait for the stop event on device 1 to complete
|
||||
hipEventSynchronize(stopEvent1);
|
||||
|
||||
// Calculate elapsed time between the events for both devices
|
||||
float milliseconds0 = 0, milliseconds1 = 0;
|
||||
hipEventElapsedTime(&milliseconds0, startEvent0, stopEvent0);
|
||||
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
|
||||
hipSetDevice(0);
|
||||
hipEventDestroy(startEvent0);
|
||||
hipEventDestroy(stopEvent0);
|
||||
hipStreamSynchronize(stream0);
|
||||
hipStreamDestroy(stream0);
|
||||
hipFree(deviceData0);
|
||||
|
||||
// Cleanup for device 1
|
||||
hipSetDevice(1);
|
||||
hipEventDestroy(startEvent1);
|
||||
hipEventDestroy(stopEvent1);
|
||||
hipStreamSynchronize(stream1);
|
||||
hipStreamDestroy(stream1);
|
||||
hipFree(deviceData1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/multi_device_synchronization.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:language: cpp
|
||||
|
||||
Peer-to-peer memory access
|
||||
===============================================================================
|
||||
@@ -257,164 +73,25 @@ applications that require frequent data exchange between GPUs, as it eliminates
|
||||
the need to transfer data through the host memory.
|
||||
|
||||
By adding peer-to-peer access to the example referenced in
|
||||
:ref:`multi_device_selection`, data can be copied between devices:
|
||||
:ref:`multi_device_selection`, data can be efficiently copied between devices.
|
||||
If peer-to-peer access is not activated, the call to :cpp:func:`hipMemcpy`
|
||||
still works but internally uses a staging buffer in host memory, which incurs a
|
||||
performance penalty.
|
||||
|
||||
.. tab-set::
|
||||
|
||||
.. tab-item:: with peer-to-peer
|
||||
|
||||
.. code-block:: cpp
|
||||
:emphasize-lines: 31-37, 51-55
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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; \
|
||||
exit(status); \
|
||||
} \
|
||||
}
|
||||
|
||||
__global__ void simpleKernel(double *data)
|
||||
{
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
data[idx] = idx * 2.0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
double* deviceData0;
|
||||
double* deviceData1;
|
||||
size_t size = 1024 * sizeof(*deviceData0);
|
||||
|
||||
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.
|
||||
hipSetDevice(deviceId0);
|
||||
hipDeviceEnablePeerAccess(deviceId1, 0);
|
||||
|
||||
hipSetDevice(deviceId1);
|
||||
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<<<1000, 128>>>(deviceData0); // 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<<<1000, 128>>>(deviceData1); // Launch kernel on device 1
|
||||
HIP_CHECK(hipDeviceSynchronize());
|
||||
|
||||
// Use peer-to-peer access
|
||||
hipSetDevice(deviceId0);
|
||||
|
||||
// Now device 0 can access memory allocated on device 1
|
||||
hipMemcpy(deviceData0, deviceData1, size, hipMemcpyDeviceToDevice);
|
||||
|
||||
// Copy result from device 0
|
||||
double hostData0[1024];
|
||||
HIP_CHECK(hipSetDevice(deviceId0));
|
||||
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
|
||||
|
||||
// Copy result from device 1
|
||||
double hostData1[1024];
|
||||
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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/p2p_memory_access.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 43-49, 63-67
|
||||
:language: cpp
|
||||
|
||||
.. tab-item:: without peer-to-peer
|
||||
|
||||
.. code-block:: cpp
|
||||
:emphasize-lines: 43-49, 53, 58
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#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; \
|
||||
exit(status); \
|
||||
} \
|
||||
}
|
||||
|
||||
__global__ void simpleKernel(double *data)
|
||||
{
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
data[idx] = idx * 2.0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
double* deviceData0;
|
||||
double* deviceData1;
|
||||
size_t size = 1024 * sizeof(*deviceData0);
|
||||
|
||||
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<<<1000, 128>>>(deviceData0); // 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<<<1000, 128>>>(deviceData1); // Launch kernel on device 1
|
||||
HIP_CHECK(hipDeviceSynchronize());
|
||||
|
||||
// Attempt to use deviceData0 on device 1 (This will not work as deviceData0 is allocated on device 0)
|
||||
HIP_CHECK(hipSetDevice(deviceId1));
|
||||
hipError_t err = hipMemcpy(deviceData1, deviceData0, size, hipMemcpyDeviceToDevice); // This should fail
|
||||
if (err != hipSuccess)
|
||||
{
|
||||
std::cout << "Error: Cannot access deviceData0 from device 1, deviceData0 is on device 0" << std::endl;
|
||||
}
|
||||
|
||||
// Copy result from device 0
|
||||
double hostData0[1024];
|
||||
HIP_CHECK(hipSetDevice(deviceId0));
|
||||
HIP_CHECK(hipMemcpy(hostData0, deviceData0, size, hipMemcpyDeviceToHost));
|
||||
|
||||
// Copy result from device 1
|
||||
double hostData1[1024];
|
||||
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 0;
|
||||
}
|
||||
.. literalinclude:: ../../tools/example_codes/p2p_memory_access_host_staging.hip
|
||||
:start-after: // [sphinx-start]
|
||||
:end-before: // [sphinx-end]
|
||||
:emphasize-lines: 55-57
|
||||
:language: cpp
|
||||
|
||||
Reference in New Issue
Block a user