|
|
|
@@ -9,6 +9,14 @@
|
|
|
|
|
Unified memory management
|
|
|
|
|
*******************************************************************************
|
|
|
|
|
|
|
|
|
|
This document covers unified memory management in HIP, which encompasses several
|
|
|
|
|
approaches that provide a single address space accessible from both CPU and GPU.
|
|
|
|
|
**Unified memory** refers to the overall architectural concept of this shared
|
|
|
|
|
address space, while **managed memory** is one specific implementation that
|
|
|
|
|
provides automatic page migration between devices. Other unified memory allocators
|
|
|
|
|
like :cpp:func:`hipMalloc()` and :cpp:func:`hipHostMalloc()` provide different
|
|
|
|
|
access patterns within the same unified address space concept.
|
|
|
|
|
|
|
|
|
|
In conventional architectures CPUs and attached devices have their own memory
|
|
|
|
|
space and dedicated physical memory backing it up, e.g. normal RAM for CPUs and
|
|
|
|
|
VRAM on GPUs. This way each device can have physical memory optimized for its
|
|
|
|
@@ -39,57 +47,79 @@ model is shown in the following figure.
|
|
|
|
|
|
|
|
|
|
Unified memory enables the access to memory located on other devices via
|
|
|
|
|
several methods, depending on whether hardware support is available or has to be
|
|
|
|
|
managed by the driver.
|
|
|
|
|
managed by the driver. CPUs can access memory allocated via :cpp:func:`hipMalloc()`,
|
|
|
|
|
providing bidirectional memory accessibility within the unified address space.
|
|
|
|
|
|
|
|
|
|
Hardware supported on-demand page migration
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
Managed memory
|
|
|
|
|
================================================================================
|
|
|
|
|
|
|
|
|
|
When a kernel on the device tries to access a memory address that is not in its
|
|
|
|
|
memory, a page-fault is triggered. The GPU then in turn requests the page from
|
|
|
|
|
the host or an other device, on which the memory is located. The page is then
|
|
|
|
|
unmapped from the source, sent to the device and mapped to the device's memory.
|
|
|
|
|
The requested memory is then available to the processes running on the device.
|
|
|
|
|
Managed Memory is an extension of the unified memory architecture in which HIP
|
|
|
|
|
monitors memory access and intelligently migrates data between device and
|
|
|
|
|
system memories, thereby improving performance and resource efficiency.
|
|
|
|
|
|
|
|
|
|
In case the device's memory is at capacity, a page is unmapped from the device's
|
|
|
|
|
memory first and sent and mapped to host memory. This enables more memory to be
|
|
|
|
|
allocated and used for a GPU, than the GPU itself has physically available.
|
|
|
|
|
When a kernel on the device tries to access a managed memory address that is
|
|
|
|
|
not in its local device memory, a page-fault is triggered. The GPU then in
|
|
|
|
|
turn requests the page from the host or other device on which the memory is
|
|
|
|
|
located. The page is unmapped from the source, sent to the device and
|
|
|
|
|
mapped to the device's memory. The requested memory is then available locally
|
|
|
|
|
to the processes running on the device, which improves performance as local
|
|
|
|
|
memory access outperforms remote memory access.
|
|
|
|
|
|
|
|
|
|
This level of unified memory support can be very beneficial for sparse accesses
|
|
|
|
|
to an array, that is not often used on the device.
|
|
|
|
|
|
|
|
|
|
Driver managed page migration
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
If the hardware does not support on-demand page migration, then all the pages
|
|
|
|
|
accessed by a kernel have to be resident on the device, so they have to be
|
|
|
|
|
migrated before the kernel is running. Since the driver can not know beforehand,
|
|
|
|
|
what parts of an array are going to be accessed, all pages of all accessed
|
|
|
|
|
arrays have to be migrated. This can lead to significant delays on the first run
|
|
|
|
|
of a kernel, on top of possibly copying more memory than is actually accessed by
|
|
|
|
|
the kernel.
|
|
|
|
|
Managed memory also expands the memory capacity available to a GPU kernel. When
|
|
|
|
|
migrating memory into the device on page-fault, if the device's memory is
|
|
|
|
|
already at capacity, a page is unmapped from the device's memory first and sent
|
|
|
|
|
and mapped to host memory. This enables more memory to be allocated and used
|
|
|
|
|
for a GPU than the GPU itself has physically available. This level of support
|
|
|
|
|
can be very beneficial, for example, for sparse accesses to an array that is
|
|
|
|
|
not often used on the device.
|
|
|
|
|
|
|
|
|
|
.. _unified memory system requirements:
|
|
|
|
|
|
|
|
|
|
System requirements
|
|
|
|
|
================================================================================
|
|
|
|
|
System requirements for managed memory
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
Unified memory is supported on Linux by all modern AMD GPUs from the Vega
|
|
|
|
|
series onward, as shown in the following table. Unified memory management can
|
|
|
|
|
be achieved by explicitly allocating managed memory using
|
|
|
|
|
:cpp:func:`hipMallocManaged` or marking variables with the ``__managed__``
|
|
|
|
|
attribute. For the latest GPUs, with a Linux kernel that supports
|
|
|
|
|
`Heterogeneous Memory Management (HMM)
|
|
|
|
|
Some AMD GPUs do not support page-faults, and thus do not support on-demand
|
|
|
|
|
page-fault driven migration. On these architectures, if the programmer prefers
|
|
|
|
|
all GPU memory accesses to be local, all pages have to migrated before the
|
|
|
|
|
kernel is dispatched, as the driver cannot know beforehand which parts of a
|
|
|
|
|
dataset are going to be accessed. This can lead to significant delays on the
|
|
|
|
|
first run of a kernel, and, in the example of a sparsely accessed array, can
|
|
|
|
|
also lead to copying more memory than is actually accessed by the kernel.
|
|
|
|
|
|
|
|
|
|
Note that on systems which do not support page-faults, managed memory APIs are
|
|
|
|
|
still accessible to the programmer, but managed memory operates in a degraded
|
|
|
|
|
fashion due to the lack of demand-driven migration. Furthermore, on these
|
|
|
|
|
systems it is still possible to use unified memory allocators that do not
|
|
|
|
|
provide managed memory features; see
|
|
|
|
|
:ref:`memory allocation approaches in unified memory` for more details.
|
|
|
|
|
|
|
|
|
|
Managed memory is supported on Linux by all modern AMD GPUs from the Vega
|
|
|
|
|
series onward, as shown in the following table. Managed memory can be
|
|
|
|
|
explicitly allocated using :cpp:func:`hipMallocManaged()` or marking variables
|
|
|
|
|
with the ``__managed__`` attribute. For the latest GPUs, with a Linux kernel
|
|
|
|
|
that supports `Heterogeneous Memory Management (HMM)
|
|
|
|
|
<https://www.kernel.org/doc/html/latest/mm/hmm.html>`_, the normal system
|
|
|
|
|
allocator can be used.
|
|
|
|
|
allocators (e.g., ``new``, ``malloc()``) can be used.
|
|
|
|
|
|
|
|
|
|
.. list-table:: Supported Unified Memory Allocators by GPU architecture
|
|
|
|
|
.. note::
|
|
|
|
|
To ensure the proper functioning of managed memory on supported GPUs, it
|
|
|
|
|
is **essential** to set the environment variable ``HSA_XNACK=1`` and use a
|
|
|
|
|
GPU kernel mode driver that supports `HMM
|
|
|
|
|
<https://www.kernel.org/doc/html/latest/mm/hmm.html>`_. Without this
|
|
|
|
|
configuration, access-driven memory migration will be disabled, and the
|
|
|
|
|
behavior will be similar to that of systems without HMM support.
|
|
|
|
|
|
|
|
|
|
.. list-table:: Managed Memory Support by GPU Architecture
|
|
|
|
|
:widths: 40, 25, 25
|
|
|
|
|
:header-rows: 1
|
|
|
|
|
:align: center
|
|
|
|
|
|
|
|
|
|
* - Architecture
|
|
|
|
|
- :cpp:func:`hipMallocManaged()`, ``__managed__``
|
|
|
|
|
- ``new``, ``malloc()``
|
|
|
|
|
- ``new``, ``malloc()``, ``allocate()``
|
|
|
|
|
* - CDNA4
|
|
|
|
|
- ✅
|
|
|
|
|
- ✅ :sup:`1`
|
|
|
|
|
* - CDNA3
|
|
|
|
|
- ✅
|
|
|
|
|
- ✅ :sup:`1`
|
|
|
|
@@ -98,7 +128,7 @@ allocator can be used.
|
|
|
|
|
- ✅ :sup:`1`
|
|
|
|
|
* - CDNA1
|
|
|
|
|
- ✅
|
|
|
|
|
- ✅ :sup:`1`
|
|
|
|
|
- ❌
|
|
|
|
|
* - RDNA1
|
|
|
|
|
- ✅
|
|
|
|
|
- ❌
|
|
|
|
@@ -113,11 +143,16 @@ allocator can be used.
|
|
|
|
|
:sup:`1` Works only with ``HSA_XNACK=1`` and kernels with HMM support. First GPU
|
|
|
|
|
access causes recoverable page-fault.
|
|
|
|
|
|
|
|
|
|
.. _unified memory allocators:
|
|
|
|
|
.. _memory allocation approaches in unified memory:
|
|
|
|
|
|
|
|
|
|
Unified memory allocators
|
|
|
|
|
Memory allocation approaches in unified memory
|
|
|
|
|
================================================================================
|
|
|
|
|
|
|
|
|
|
While managed memory provides automatic migration, unified memory encompasses
|
|
|
|
|
several allocation methods, each with different access patterns and migration
|
|
|
|
|
behaviors. The following section covers all available unified memory allocation
|
|
|
|
|
approaches, including but not limited to managed memory APIs.
|
|
|
|
|
|
|
|
|
|
Support for the different unified memory allocators depends on the GPU
|
|
|
|
|
architecture and on the system. For more information, see :ref:`unified memory
|
|
|
|
|
system requirements` and :ref:`checking unified memory support`.
|
|
|
|
@@ -133,17 +168,17 @@ system requirements` and :ref:`checking unified memory support`.
|
|
|
|
|
|
|
|
|
|
- **System allocated unified memory**
|
|
|
|
|
|
|
|
|
|
Starting with CDNA2, the ``new`` and ``malloc()`` system allocators allow
|
|
|
|
|
Starting with CDNA2, the ``new``, ``malloc()``, and ``allocate()`` (Fortran) system allocators allow
|
|
|
|
|
you to reserve unified memory. The system allocator is more versatile and
|
|
|
|
|
offers an easy transition for code written for CPUs to HIP code as the
|
|
|
|
|
same system allocation API is used.
|
|
|
|
|
same system allocation API is used. Memory allocated by these allocators can
|
|
|
|
|
be registered to be accessible on device using :cpp:func:`hipHostRegister()`.
|
|
|
|
|
|
|
|
|
|
To ensure the proper functioning of system allocated unified memory on supported
|
|
|
|
|
GPUs, it is essential to set the environment variable ``HSA_XNACK=1`` and use
|
|
|
|
|
a GPU kernel mode driver that supports HMM
|
|
|
|
|
<https://www.kernel.org/doc/html/latest/mm/hmm.html>`_. Without this
|
|
|
|
|
configuration, the behavior will be similar to that of systems without HMM
|
|
|
|
|
support.
|
|
|
|
|
- **HIP allocated non-managed memory**
|
|
|
|
|
|
|
|
|
|
:cpp:func:`hipMalloc()` and :cpp:func:`hipHostMalloc()` are dynamic memory
|
|
|
|
|
allocators available on all GPUs with unified memory support. Memory
|
|
|
|
|
allocated by these allocators is not migrated between device and host memory.
|
|
|
|
|
|
|
|
|
|
The table below illustrates the expected behavior of managed and unified memory
|
|
|
|
|
functions on ROCm and CUDA, both with and without HMM support.
|
|
|
|
@@ -161,10 +196,10 @@ functions on ROCm and CUDA, both with and without HMM support.
|
|
|
|
|
- Access outside the origin without HMM or ``HSA_XNACK=0``
|
|
|
|
|
- Allocation origin with HMM and ``HSA_XNACK=1``
|
|
|
|
|
- Access outside the origin with HMM and ``HSA_XNACK=1``
|
|
|
|
|
* - ``new``, ``malloc()``
|
|
|
|
|
* - ``new``, ``malloc()``, ``allocate()``
|
|
|
|
|
- host
|
|
|
|
|
- not accessible on device
|
|
|
|
|
- host
|
|
|
|
|
- first touch
|
|
|
|
|
- page-fault migration
|
|
|
|
|
* - :cpp:func:`hipMalloc()`
|
|
|
|
|
- device
|
|
|
|
@@ -174,13 +209,13 @@ functions on ROCm and CUDA, both with and without HMM support.
|
|
|
|
|
* - :cpp:func:`hipMallocManaged()`, ``__managed__``
|
|
|
|
|
- pinned host
|
|
|
|
|
- zero copy [zc]_
|
|
|
|
|
- host
|
|
|
|
|
- first touch
|
|
|
|
|
- page-fault migration
|
|
|
|
|
* - :cpp:func:`hipHostRegister()`
|
|
|
|
|
- undefined behavior
|
|
|
|
|
- undefined behavior
|
|
|
|
|
- host
|
|
|
|
|
- page-fault migration
|
|
|
|
|
- pinned host
|
|
|
|
|
- zero copy [zc]_
|
|
|
|
|
- pinned host
|
|
|
|
|
- zero copy [zc]_
|
|
|
|
|
* - :cpp:func:`hipHostMalloc()`
|
|
|
|
|
- pinned host
|
|
|
|
|
- zero copy [zc]_
|
|
|
|
@@ -236,9 +271,9 @@ functions on ROCm and CUDA, both with and without HMM support.
|
|
|
|
|
Checking unified memory support
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
The following device attributes can offer information about which :ref:`unified
|
|
|
|
|
memory allocators` are supported. The attribute value is 1 if the functionality
|
|
|
|
|
is supported, and 0 if it is not supported.
|
|
|
|
|
The following device attributes can offer information about which :ref:`memory
|
|
|
|
|
allocation approaches in unified memory` are supported. The attribute value is
|
|
|
|
|
1 if the functionality is supported, and 0 if it is not supported.
|
|
|
|
|
|
|
|
|
|
.. list-table:: Device attributes for unified memory management
|
|
|
|
|
:widths: 40, 60
|
|
|
|
@@ -364,10 +399,11 @@ explicit memory management example is presented in the last tab.
|
|
|
|
|
.. tab-item:: new
|
|
|
|
|
|
|
|
|
|
.. code-block:: cpp
|
|
|
|
|
:emphasize-lines: 20-23
|
|
|
|
|
:emphasize-lines: 21-24
|
|
|
|
|
|
|
|
|
|
#include <hip/hip_runtime.h>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <new>
|
|
|
|
|
|
|
|
|
|
#define HIP_CHECK(expression) \
|
|
|
|
|
{ \
|
|
|
|
@@ -386,10 +422,10 @@ explicit memory management example is presented in the last tab.
|
|
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
// 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;
|
|
|
|
@@ -404,10 +440,10 @@ explicit memory management example is presented in the last tab.
|
|
|
|
|
// Prints the result.
|
|
|
|
|
std::cout << *a << " + " << *b << " = " << *c << std::endl;
|
|
|
|
|
|
|
|
|
|
// Cleanup allocated memory.
|
|
|
|
|
delete[] a;
|
|
|
|
|
delete[] b;
|
|
|
|
|
delete[] c;
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
@@ -501,9 +537,24 @@ Performance optimizations for unified memory
|
|
|
|
|
There are several ways, in which the developer can guide the runtime to reduce
|
|
|
|
|
copies between devices, in order to improve performance.
|
|
|
|
|
|
|
|
|
|
With ``numactl --membind`` bindings, developers can control where physical
|
|
|
|
|
allocation occurs by restricting memory allocation to specific NUMA nodes.
|
|
|
|
|
This approach can reduce or eliminate the need for explicit data prefetching
|
|
|
|
|
since memory is allocated in the desired location from the start.
|
|
|
|
|
|
|
|
|
|
Data prefetching
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
Data prefetching is not always an optimization and can slow down execution,
|
|
|
|
|
as the API takes time to execute. If the memory is already in the right
|
|
|
|
|
place, prefetching will waste time. Users should profile their code to
|
|
|
|
|
verify whether prefetching is beneficial for their specific use case.
|
|
|
|
|
|
|
|
|
|
When prefetching is beneficial, developers can consider setting different default
|
|
|
|
|
locations for different devices and using prefetch between them, which can help
|
|
|
|
|
eliminate IPC communication overhead when memory moves between devices.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|