Introduction
This lesson will introduce the Heterogeneous-compute Interface for Portability (HIP) C++ API and explain how it can be used to write portable accelerated programs that can be run on a variety of hardware. In particular, we will look at its relation to the Nvidia CUDA API and how we can port existing code from CUDA to HIP.
We will see several methods for this conversion (or hipification) which will be appropriate in different situations, depending on code complexity. Some common pitfalls in these conversions will be noted, as well as ways to avoid or mitigate them.
Learning objectives
By the end of this module, you will be able to:
- Understand what HIP is
- Select an appropriate method for converting existing CUDA accelerated code to HIP
- Use the tools provided within ROCm to analyse CUDA code prior to conversion to assess complexity
- Identify common issues with CUDA to HIP conversion.
Presentation
This presentation, delivered by Dr Tom Papatheodore, introduces how to port from CUDA.
Please continue the lesson with the dedicated exercises shown below.
The exercises in this lesson will demonstrate different ways to port code written with the CUDA API to AMD's Heterogeneous-compute Interface for Portability (HIP). Please note that the details of the code and parallel implementation (either in CUDA or HIP) will not be examined in this lesson, which concentrates instead solely on conversion strategies. Details on HIP's principles and how to apply them to your code will be discussed at length later in this course.
For now, let's move into the exercise directory and set it up as the base directory for this lesson. We will also set up the ROCm path to the compiler version we will use for these exercises.
Bash
cd $HOME/DiRAC-AMD-GPU/notebooks/03-HIP/3a-HIP_porting_from_cuda/
export base_dir=$PWD
export ROCM_PATH=/opt/rocm-7.2.4/
Manually converting code to HIP
The most basic method for converting code from CUDA to HIP is to do so by hand. CUDA and HIP share a very similar API; in the vast majority of cases, replacing the cuda term in an expression or call with hip will result in a working HIP API call that carries out the same process.
For the first exercise, you will carry out this conversion yourself on a simple piece of CUDA code. First, let's move into the exercise directory.
Bash
cd $base_dir/HIPIFY
Choose one of the codes in this directory: nbody-block.cu, nbody-orig.cu or nbody-soa.cu. Copy that file changing its extension to *.cpp, then manually convert it from CUDA to HIP by replacing all cuda commands with hip. Finally, add the hip runtime header to the includes #include "hip/hip_runtime.h".
You can compile and run your modified code using one of the code segments below, in order to check your work. Please note that some warnings related to the return values of hip calls are expected (we will address them in future lessons).
Bash
# For n-body block
hipcc nbody-block.cpp -o nbody-block
./nbody-block
Bash
# For n-body orig
hipcc nbody-orig.cpp -o nbody-orig
./nbody-orig
Bash
# For n-body soa
hipcc nbody-soa.cpp -o nbody-soa
./nbody-soa
Converting small pieces of code or individual kernels from CUDA to HIP by hand is simple enough, but once the code-base becomes large, or the CUDA calls frequent, the task can become tedious and error-prone.
Various tools have been created to ease this process by carrying out the conversion automatically. The following sections will show examples of two such implementations.
Using HIPIFY
HIPIFY is a ROCm tool designed to automatically convert CUDA-enabled C++ code to HIP. It comes in two flavours:
hipify-pearl: A tool that carries out very simple conversions by replacing cuda calls with hip. Carries out no tests for correctness and may require manual intervention on the outputs. Recommended for a quick scan of projects or for simple codes.hipify-clang: A more robust tool that parses CUDA code into an Abstract Syntax Tree (AST) before carrying out the HIP conversion. It can create warnings where the translation might not work as intended. It carries out a thorough translation of the code, but is much heavier than hipify-perl.
For this exercise, we will use the hipify-pearl tool on the code we manually converted in the previous section. Let's begin by making sure that we are in the correct directory:
Bash
cd $base_dir/HIPIFY
Now let's try using the hipify-pearl tool. We will start by adding the -examine command line argument, which gives an overview of the HIP APIs that will be converted. Let's see what this looks like:
Bash
hipify-perl -examine nbody-block.cu
output
[HIPIFY] info: file 'nbody-block.cu' statistics:
CONVERTED refs count: 11
TOTAL lines of code: 91
WARNINGS: 0
[HIPIFY] info: CONVERTED refs by names:
__syncthreads => __syncthreads: 2
cudaFree => hipFree: 1
cudaMalloc => hipMalloc: 1
cudaMemcpy => hipMemcpy: 2
cudaMemcpyDeviceToHost => hipMemcpyDeviceToHost: 1
cudaMemcpyHostToDevice => hipMemcpyHostToDevice: 1
rsqrtf => rsqrtf: 1
You should see the above output, complete with information on the number of lines to be converted and a list of the converted calls by name. This gives a good idea of how much work is to be done in the conversion.
Now let's run the conversion itself. Running the hipify-pearl tool will print the converted output to the terminal, so we should pipe the output into an appropriately named file. Do this with the nbody-block.cu code now:
Bash
hipify-perl nbody-block.cu > nbody-block.cpp
Let's compile and run the code to check the output.
Bash
hipcc nbody-block.cpp -o nbody-block
./nbody-block
The code will have successfully been converted to and run using HIP.
If you would prefer to convert files in-place, hipify-pearl provides the argument -in place to do so, but be aware that this will overwrite the file and could cause damage to your code! hipify-clang converts the code in-place automatically, so it is usually advised to make a copy of the code by running the tool on it.
Automatic conversion tools are very useful, but will not necessarily do the entire job for you. Some things to note when doing the conversion:
- Nvidia and AMD hardware differ on some levels - the warp size of Nvidia cards, for example, is 32, whereas the wave front for AMD cards is typically 64. This may need to be changed manually within the code if it is hard-coded
- Certain CUDA function do not yet have HIP equivalents
- The HIPIFY tools cannot handline inline PTX assemble or CUDA intrinsics
- HIPIFY tools do not touch the build system; make files and CMakeLists will need to have appropriate flags added where necessary.
HIPifly example: Vector addition
Another possible conversion method is the HIPifly method, which carries out the CUDA-to_HIP conversion on the fly during compilation. There is an example code for this in the hipifly directory; let's move into that directory and look at the contents of the source code now:
Bash
cd $base_dir/hipifly/
make clean
ls src/
All of the GPU functions are defined within gpu_functions.cu. If you look in that file, you will see only CUDA calls in the code. With the default make action, this file will be built with nvcc and be compiled using CUDA for Nvidia devices.
You may note, however, that there is a pre-compiler flag option ENABLE_HIP that includes the hipifly.h file from the same directory. Including this header will automatically replace the CUDA calls with analogous HIP functions during compile time, allowing for code buildable in multiple paradigms. Practically, this is passed to the code using the command line argument DFLAGS=-DENABLE_HIP when running make. This also allows the Makefile to correctly load the HIP or CUDA compiler flags and libraries, depending on compilation preference.
Let's compile and run this code now:
Bash
make DFLAGS=-DENABLE_HIP
./vector_add
Output
GPU vector addition
Number of available devices 2
Device id: 0
Device: 0 name: AMD Instinct MI210
N elements: 536870912
Kernel executed in 10.75 milliseconds.
BW = 1115.9 GB/s.
Validation PASSED.
Finished
This on-the-fly conversion is useful when not wanting to convert entire codebases, but it has possible drawbacks. It only works when the calls are identical in CUDA and HIP, and makes no changes to CUDA-specific hard-coded values. Whether it is worthwhile or performant for your codebase should be evaluated on a case-by-case basis.
Q1:
Q2:
Q3:
Q4:
Conclusions
This lesson has provided an introduction on how to convert existing code from CUDA to HIP. This is useful as HIP is a portable API that can be run with different hardware-specific backends, depending on the target device.
In the next section, we will learn about HIP specific programming concepts and how to write native HIP code.
Key Points
- HIP is AMD's open-source C++ API that provides a CUDA-like syntax, enabling developers to write portable code that runs on both AMD and NVIDIA GPUs using the
hipcccompiler. - Use automated conversion tools like
hipify-perlfor quick string-based translation (cuda→hip) orhipify-clangfor more robust, semantic-aware conversion of entire projects, though manual adjustments may still be needed. - Directory-level scripts like
hipconvertinplace-perl.shrecursively apply hipification across source trees, preserving original files with.prehipextensions while generating converted HIP versions. - Watch for hardware-specific assumptions like warp size (32 in CUDA vs. 64 in HIP) and unsupported features like PTX assembly or certain intrinsics that require manual rewriting using GCN assembly or HIP equivalents.
- For maximum portability without code changes, consider the HIPIFLY header interception approach which redirects CUDA API calls to HIP at compile-time, though this requires avoiding CUDA-specific hardware optimisations.
- Remember that build systems (CMake, Makefiles) require manual updates to reference HIP compilers and flags, though CMake 3.21+ includes native HIP support through
enable_language(HIP).