File size: 8,388 Bytes
4d5ab1c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | /*
Common utilities for CUDA code.
*/
#ifndef CUDA_COMMON_H
#define CUDA_COMMON_H
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string>
#include <type_traits> // std::bool_constant
#include <cuda_runtime.h>
#include <nvtx3/nvToolsExt.h>
#include <nvtx3/nvToolsExtCudaRt.h>
#include <cuda_profiler_api.h>
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include "utils.h"
// ----------------------------------------------------------------------------
// Global defines and settings
// Device properties of the CUDA device used in this process
// defined as extern here because the individual kernels wish to use it
// but it is actually created and instantiated in the main program file
extern cudaDeviceProp deviceProp;
// WarpSize is not a compile time constant
// Defining here like this possibly allows the compiler to optimize better
#define WARP_SIZE 32U
// try to make sure that 2 blocks fit on A100/H100 to maximise latency tolerance
// this needs to be defines rather than queried to be used for __launch_bounds__
#if __CUDA_ARCH__ == 800 || __CUDA_ARCH__ >= 900
#define MAX_1024_THREADS_BLOCKS 2
#else
#define MAX_1024_THREADS_BLOCKS 1
#endif
// convenience macro for calculating grid/block dimensions for kernels
#define CEIL_DIV(M, N) (((M) + (N)-1) / (N))
// short-cuts for compile-time boolean values that can be used as function arguments
constexpr std::bool_constant<true> True;
constexpr std::bool_constant<true> False;
// ----------------------------------------------------------------------------
// Error checking
// CUDA error checking. Underscore added so this function can be called directly not just via macro
inline void cudaCheck_(cudaError_t error, const char *file, int line) {
if (error != cudaSuccess) {
printf("[CUDA ERROR] at file %s:%d:\n%s\n", file, line, cudaGetErrorString(error));
exit(EXIT_FAILURE);
}
};
#define cudaCheck(err) (cudaCheck_(err, __FILE__, __LINE__))
// like cudaFree, but checks for errors _and_ resets the pointer.
template<class T>
inline void cudaFreeCheck(T** ptr, const char *file, int line) {
cudaError_t error = cudaFree(*ptr);
if (error != cudaSuccess) {
printf("[CUDA ERROR] at file %s:%d:\n%s\n", file, line, cudaGetErrorString(error));
exit(EXIT_FAILURE);
}
*ptr = nullptr;
}
#define cudaFreeCheck(ptr) (cudaFreeCheck(ptr, __FILE__, __LINE__))
// ----------------------------------------------------------------------------
// CUDA Precision settings and defines
enum PrecisionMode {
PRECISION_FP32,
PRECISION_FP16,
PRECISION_BF16
};
// Specific configurations based on the enabled precision
#if defined(ENABLE_FP32)
typedef float floatX;
#define PRECISION_MODE PRECISION_FP32
// use fp16 (note: this may require gradient scaler, currently not implemented!)
#elif defined(ENABLE_FP16)
typedef half floatX;
#define PRECISION_MODE PRECISION_FP16
#else // Default to bfloat16
typedef __nv_bfloat16 floatX;
#define PRECISION_MODE PRECISION_BF16
#endif
// ----------------------------------------------------------------------------
// Load and store with streaming cache hints
// Older nvcc does not provide __ldcs and __stcs for bfloat16, despite these
// actually just being unsigned shorts. We need to be careful here to only define
// our own versions if none already exist, otherwise the compiler will complain.
// If not, you easily get "no viable overload" (for sm52) and "function already exists" (sm_80)
#if defined(ENABLE_BF16) && (__CUDACC_VER_MAJOR__ < 12) && !((__CUDA_ARCH__ >= 800) || !defined(__CUDA_ARCH__))
__device__ floatX __ldcs(const floatX* address) {
unsigned short bf = __ldcs(reinterpret_cast<const unsigned short*>(address));
return __nv_bfloat16_raw{bf};
}
__device__ void __stcs(floatX* address, floatX value) {
__stcs(reinterpret_cast<unsigned short*>(address), ((__nv_bfloat16_raw)value).x);
}
#endif
// ----------------------------------------------------------------------------
// Profiler utils
class NvtxRange {
public:
NvtxRange(const char* s) { nvtxRangePush(s); }
NvtxRange(const std::string& base_str, int number) {
std::string range_string = base_str + " " + std::to_string(number);
nvtxRangePush(range_string.c_str());
}
~NvtxRange() { nvtxRangePop(); }
};
#define NVTX_RANGE_FN() NvtxRange nvtx_range(__FUNCTION__)
// ----------------------------------------------------------------------------
// Utilities to Read & Write between CUDA memory <-> files
// copy num_bytes from device pointer src into file dest, using double buffering running on the given stream.
inline void device_to_file(FILE* dest, void* src, size_t num_bytes, size_t buffer_size, cudaStream_t stream) {
// allocate pinned buffer for faster, async transfer
char* buffer_space;
cudaCheck(cudaMallocHost(&buffer_space, 2*buffer_size));
// split allocation in two
void* read_buffer = buffer_space;
void* write_buffer = buffer_space + buffer_size;
// prime the read buffer; first copy means we have to wait
char* gpu_read_ptr = (char*)src;
size_t copy_amount = std::min(buffer_size, num_bytes);
cudaCheck(cudaMemcpyAsync(read_buffer, gpu_read_ptr, copy_amount, cudaMemcpyDeviceToHost, stream));
cudaCheck(cudaStreamSynchronize(stream));
size_t rest_bytes = num_bytes - copy_amount;
size_t write_buffer_size = copy_amount;
gpu_read_ptr += copy_amount;
std::swap(read_buffer, write_buffer);
// now the main loop; as long as there are bytes left
while(rest_bytes > 0) {
// initiate next read
copy_amount = std::min(buffer_size, rest_bytes);
cudaCheck(cudaMemcpyAsync(read_buffer, gpu_read_ptr, copy_amount, cudaMemcpyDeviceToHost, stream));
// while this is going on, transfer the write buffer to disk
fwriteCheck(write_buffer, 1, write_buffer_size, dest);
cudaCheck(cudaStreamSynchronize(stream)); // wait for both buffers to be ready.
std::swap(read_buffer, write_buffer);
rest_bytes -= copy_amount;
write_buffer_size = copy_amount;
gpu_read_ptr += copy_amount;
}
// make sure to write the last remaining write buffer
fwriteCheck(write_buffer, 1, write_buffer_size, dest);
cudaCheck(cudaFreeHost(buffer_space));
}
// copy num_bytes from file src into device pointer dest, using double buffering running on the given stream.
inline void file_to_device(void* dest, FILE* src, size_t num_bytes, size_t buffer_size, cudaStream_t stream) {
// allocate pinned buffer for faster, async transfer
// from the docs (https://developer.download.nvidia.com/compute/DevZone/docs/html/C/doc/html/group__CUDART__HIGHLEVEL_ge439496de696b166ba457dab5dd4f356.html)
// WC memory is a good option for buffers that will be written by the CPU and read by the device via mapped pinned memory or host->device transfers.
char* buffer_space;
cudaCheck(cudaMallocHost(&buffer_space, 2*buffer_size, cudaHostAllocWriteCombined));
// split allocation in two
void* read_buffer = buffer_space;
void* write_buffer = buffer_space + buffer_size;
// prime the read buffer;
char* gpu_write_ptr = (char*)dest;
size_t copy_amount = std::min(buffer_size, num_bytes);
freadCheck(read_buffer, 1, copy_amount, src);
size_t rest_bytes = num_bytes - copy_amount;
size_t write_buffer_size = copy_amount;
std::swap(read_buffer, write_buffer);
// now the main loop; as long as there are bytes left
while(rest_bytes > 0) {
// initiate next read
copy_amount = std::min(buffer_size, rest_bytes);
cudaCheck(cudaMemcpyAsync(gpu_write_ptr, write_buffer, write_buffer_size, cudaMemcpyHostToDevice, stream));
gpu_write_ptr += write_buffer_size;
// while this is going on, read from disk
freadCheck(read_buffer, 1, copy_amount, src);
cudaCheck(cudaStreamSynchronize(stream)); // wait for both buffers to be ready.
std::swap(read_buffer, write_buffer);
rest_bytes -= copy_amount;
write_buffer_size = copy_amount;
}
// copy the last remaining write buffer to gpu
cudaCheck(cudaMemcpyAsync(gpu_write_ptr, write_buffer, write_buffer_size, cudaMemcpyHostToDevice, stream));
cudaCheck(cudaStreamSynchronize(stream));
cudaCheck(cudaFreeHost(buffer_space));
}
#endif // CUDA_COMMON_H |