blob: c0e11098797366524c48b294122522496fc3acd8 [file] [log] [blame]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#if defined(EIGEN_USE_GPU) && !defined(EIGEN_CXX11_TENSOR_TENSOR_DEVICE_HIP_H)
#define EIGEN_CXX11_TENSOR_TENSOR_DEVICE_HIP_H
#if defined(EIGEN_HIPCC)
#include "hip/hip_runtime.h"
#include "hip/hip_runtime_api.h"
#endif
#include <unistd.h> //for sleep function
namespace Eigen {
static const int kHipScratchSize = 1024;
// This defines an interface that GPUDevice can take to use
// HIP streams underneath.
class StreamInterface {
public:
virtual ~StreamInterface() {}
virtual const hipStream_t& stream() const = 0;
virtual const hipDeviceProp_t& deviceProperties() const = 0;
// Allocate memory on the actual device where the computation will run
virtual void* allocate(size_t num_bytes) const = 0;
virtual void deallocate(void* buffer) const = 0;
// Return a scratchpad buffer of size 1k
virtual void* scratchpad() const = 0;
// Return a semaphore. The semaphore is initially initialized to 0, and
// each kernel using it is responsible for resetting to 0 upon completion
// to maintain the invariant that the semaphore is always equal to 0 upon
// each kernel start.
virtual unsigned int* semaphore() const = 0;
};
static hipDeviceProp_t* m_deviceProperties;
static bool m_devicePropInitialized = false;
static void initializeDeviceProp() {
if (!m_devicePropInitialized) {
// Attempts to ensure proper behavior in the case of multiple threads
// calling this function simultaneously. This would be trivial to
// implement if we could use std::mutex, but unfortunately mutex don't
// compile with nvcc, so we resort to atomics and thread fences instead.
// Note that if the caller uses a compiler that doesn't support c++11 we
// can't ensure that the initialization is thread safe.
#if 0 && __cplusplus >= 201103L
static std::atomic<bool> first(true);
if (first.exchange(false)) {
#else
static bool first = true;
if (first) {
first = false;
#endif
// We're the first thread to reach this point.
int num_devices;
hipError_t status = hipGetDeviceCount(&num_devices);
if (status != hipSuccess) {
std::cerr << "Failed to get the number of HIP devices: "
<< hipGetErrorString(status)
<< std::endl;
assert(status == hipSuccess);
}
m_deviceProperties = new hipDeviceProp_t[num_devices];
for (int i = 0; i < num_devices; ++i) {
status = hipGetDeviceProperties(&m_deviceProperties[i], i);
if (status != hipSuccess) {
std::cerr << "Failed to initialize HIP device #"
<< i
<< ": "
<< hipGetErrorString(status)
<< std::endl;
assert(status == hipSuccess);
}
}
#if 0 && __cplusplus >= 201103L
std::atomic_thread_fence(std::memory_order_release);
#endif
m_devicePropInitialized = true;
} else {
// Wait for the other thread to inititialize the properties.
while (!m_devicePropInitialized) {
#if 0 && __cplusplus >= 201103L
std::atomic_thread_fence(std::memory_order_acquire);
#endif
sleep(1);
}
}
}
}
static const hipStream_t default_stream = 0x00;//TODO: Use hipStreamDefault instead of 0x00;
class HipStreamDevice : public StreamInterface {
public:
// Use the default stream on the current device
HipStreamDevice() : stream_(&default_stream), scratch_(NULL), semaphore_(NULL) {
hipGetDevice(&device_);
initializeDeviceProp();
}
// Use the default stream on the specified device
HipStreamDevice(int device) : stream_(&default_stream), device_(device), scratch_(NULL), semaphore_(NULL) {
initializeDeviceProp();
}
// Use the specified stream. Note that it's the
// caller responsibility to ensure that the stream can run on
// the specified device. If no device is specified the code
// assumes that the stream is associated to the current gpu device.
HipStreamDevice(const hipStream_t* stream, int device = -1)
: stream_(stream), device_(device), scratch_(NULL), semaphore_(NULL) {
if (device < 0) {
hipGetDevice(&device_);
} else {
int num_devices;
hipError_t err = hipGetDeviceCount(&num_devices);
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
assert(device < num_devices);
device_ = device;
}
initializeDeviceProp();
}
virtual ~HipStreamDevice() {
if (scratch_) {
deallocate(scratch_);
}
}
const hipStream_t& stream() const { return *stream_; }
const hipDeviceProp_t& deviceProperties() const {
return m_deviceProperties[device_];
}
virtual void* allocate(size_t num_bytes) const {
hipError_t err = hipSetDevice(device_);
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
void* result;
err = hipMalloc(&result, num_bytes);
assert(err == hipSuccess);
assert(result != NULL);
return result;
}
virtual void deallocate(void* buffer) const {
hipError_t err = hipSetDevice(device_);
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
assert(buffer != NULL);
err = hipFree(buffer);
assert(err == hipSuccess);
}
virtual void* scratchpad() const {
if (scratch_ == NULL) {
scratch_ = allocate(kHipScratchSize + sizeof(unsigned int));
}
return scratch_;
}
virtual unsigned int* semaphore() const {
if (semaphore_ == NULL) {
char* scratch = static_cast<char*>(scratchpad()) + kHipScratchSize;
semaphore_ = reinterpret_cast<unsigned int*>(scratch);
//hipError_t err = hipMemsetAsync(semaphore_, 0, sizeof(unsigned int), *stream_);
hipError_t err = hipMemset(semaphore_, 0, sizeof(unsigned int));
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
}
return semaphore_;
}
private:
const hipStream_t* stream_;
int device_;
mutable void* scratch_;
mutable unsigned int* semaphore_;
};
struct GpuDevice {
// The StreamInterface is not owned: the caller is
// responsible for its initialization and eventual destruction.
explicit GpuDevice(const StreamInterface* stream) : stream_(stream), max_blocks_(INT_MAX) {
eigen_assert(stream);
}
explicit GpuDevice(const StreamInterface* stream, int num_blocks) : stream_(stream), max_blocks_(num_blocks) {
eigen_assert(stream);
}
// TODO(bsteiner): This is an internal API, we should not expose it.
EIGEN_STRONG_INLINE const hipStream_t& stream() const {
return stream_->stream();
}
EIGEN_STRONG_INLINE void* allocate(size_t num_bytes) const {
return stream_->allocate(num_bytes);
}
EIGEN_STRONG_INLINE void deallocate(void* buffer) const {
stream_->deallocate(buffer);
}
EIGEN_STRONG_INLINE void* scratchpad() const {
return stream_->scratchpad();
}
EIGEN_STRONG_INLINE unsigned int* semaphore() const {
return stream_->semaphore();
}
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void memcpy(void* dst, const void* src, size_t n) const {
#if !defined(EIGEN_HIP_DEVICE_COMPILE)
hipError_t err = hipMemcpyAsync(dst, src, n, hipMemcpyDeviceToDevice,
stream_->stream());
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
#else
eigen_assert(false && "The default device should be used instead to generate kernel code");
#endif
}
EIGEN_STRONG_INLINE void memcpyHostToDevice(void* dst, const void* src, size_t n) const {
hipError_t err =
hipMemcpyAsync(dst, src, n, hipMemcpyHostToDevice, stream_->stream());
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
}
EIGEN_STRONG_INLINE void memcpyDeviceToHost(void* dst, const void* src, size_t n) const {
hipError_t err =
hipMemcpyAsync(dst, src, n, hipMemcpyDeviceToHost, stream_->stream());
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
}
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void memset(void* buffer, int c, size_t n) const {
#if !defined(EIGEN_HIP_DEVICE_COMPILE)
//TODO:hipError_t err = hipMemsetAsync(buffer, c, n, stream_->stream());
hipError_t err = hipMemset(buffer, c, n);
EIGEN_UNUSED_VARIABLE(err)
assert(err == hipSuccess);
#else
eigen_assert(false && "The default device should be used instead to generate kernel code");
#endif
}
EIGEN_STRONG_INLINE size_t numThreads() const {
// FIXME
return 32;
}
EIGEN_STRONG_INLINE size_t firstLevelCacheSize() const {
// FIXME
return 48*1024;
}
EIGEN_STRONG_INLINE size_t lastLevelCacheSize() const {
// We won't try to take advantage of the l2 cache for the time being, and
// there is no l3 cache on hip devices.
return firstLevelCacheSize();
}
// FIXME - this will move into HIP
#if defined(EIGEN_HIP_DEVICE_COMPILE)
#undef assert
#define assert(COND)
#endif
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void synchronize() const {
#if defined(EIGEN_HIPCC) && \
!defined(EIGEN_HIP_DEVICE_COMPILE)
hipError_t err = hipStreamSynchronize(stream_->stream());
if (err != hipSuccess) {
std::cerr << "Error detected in HIP stream: "
<< hipGetErrorString(err)
<< std::endl;
assert(err == hipSuccess);
}
#else
assert(false && "The default device should be used instead to generate kernel code");
#endif
}
EIGEN_STRONG_INLINE int getNumHipMultiProcessors() const {
return stream_->deviceProperties().multiProcessorCount;
}
EIGEN_STRONG_INLINE int maxHipThreadsPerBlock() const {
return stream_->deviceProperties().maxThreadsPerBlock;
}
EIGEN_STRONG_INLINE int maxHipThreadsPerMultiProcessor() const {
return stream_->deviceProperties().maxThreadsPerMultiProcessor;
}
EIGEN_STRONG_INLINE int sharedMemPerBlock() const {
return stream_->deviceProperties().sharedMemPerBlock;
}
EIGEN_STRONG_INLINE int majorDeviceVersion() const {
return stream_->deviceProperties().major;
}
EIGEN_STRONG_INLINE int minorDeviceVersion() const {
return stream_->deviceProperties().minor;
}
EIGEN_STRONG_INLINE int maxBlocks() const {
return max_blocks_;
}
// This function checks if the HIP runtime recorded an error for the
// underlying stream device.
inline bool ok() const {
#if defined(EIGEN_HIPCC)
hipError_t error = hipStreamQuery(stream_->stream());
return (error == hipSuccess) || (error == hipErrorNotReady);
#else
return false;
#endif
}
private:
const StreamInterface* stream_;
int max_blocks_;
};
#define LAUNCH_HIP_KERNEL(kernel, gridsize, blocksize, sharedmem, device, ...) \
hipLaunchKernelGGL(HIP_KERNEL_NAME(kernel), dim3(gridsize), dim3(blocksize), (sharedmem), (device).stream(), (__VA_ARGS__)); \
assert(hipGetLastError() == hipSuccess);
// FIXME: Should be device and kernel specific.
#if defined(EIGEN_HIPCC)
static EIGEN_DEVICE_FUNC inline void setHipSharedMemConfig(hipSharedMemConfig config) {
#if !defined(EIGEN_HIP_DEVICE_COMPILE)
hipError_t status = hipDeviceSetSharedMemConfig(config);
EIGEN_UNUSED_VARIABLE(status)
assert(status == hipSuccess);
#else
EIGEN_UNUSED_VARIABLE(config)
#endif
}
#endif
} // end namespace Eigen
#endif // EIGEN_CXX11_TENSOR_TENSOR_DEVICE_HIP_H