blob: b4d7b41b7f4ae2d9a0e79482c6f53ee6ede18b01 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file cuda_device_api.cc
* \brief GPU specific API
*/
#include <cuda.h>
#include <cuda_runtime.h>
#include <dmlc/thread_local.h>
#include <tvm/runtime/device_api.h>
#include <tvm/runtime/profiling.h>
#include <tvm/runtime/registry.h>
#include <cstring>
#include "cuda_common.h"
namespace tvm {
namespace runtime {
class CUDADeviceAPI final : public DeviceAPI {
public:
void SetDevice(Device dev) final { CUDA_CALL(cudaSetDevice(dev.device_id)); }
void GetAttr(Device dev, DeviceAttrKind kind, TVMRetValue* rv) final {
int value = 0;
switch (kind) {
case kExist:
value = (cudaDeviceGetAttribute(&value, cudaDevAttrMaxThreadsPerBlock, dev.device_id) ==
cudaSuccess);
break;
case kMaxThreadsPerBlock: {
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrMaxThreadsPerBlock, dev.device_id));
break;
}
case kWarpSize: {
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrWarpSize, dev.device_id));
break;
}
case kMaxSharedMemoryPerBlock: {
CUDA_CALL(
cudaDeviceGetAttribute(&value, cudaDevAttrMaxSharedMemoryPerBlock, dev.device_id));
break;
}
case kComputeVersion: {
std::ostringstream os;
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrComputeCapabilityMajor, dev.device_id));
os << value << ".";
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrComputeCapabilityMinor, dev.device_id));
os << value;
*rv = os.str();
return;
}
case kDeviceName: {
std::string name(256, 0);
CUDA_DRIVER_CALL(cuDeviceGetName(&name[0], name.size(), dev.device_id));
name.resize(strlen(name.c_str()));
*rv = std::move(name);
return;
}
case kMaxClockRate: {
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrClockRate, dev.device_id));
break;
}
case kMultiProcessorCount: {
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrMultiProcessorCount, dev.device_id));
break;
}
case kMaxThreadDimensions: {
int dims[3];
CUDA_CALL(cudaDeviceGetAttribute(&dims[0], cudaDevAttrMaxBlockDimX, dev.device_id));
CUDA_CALL(cudaDeviceGetAttribute(&dims[1], cudaDevAttrMaxBlockDimY, dev.device_id));
CUDA_CALL(cudaDeviceGetAttribute(&dims[2], cudaDevAttrMaxBlockDimZ, dev.device_id));
std::stringstream ss; // use json string to return multiple int values;
ss << "[" << dims[0] << ", " << dims[1] << ", " << dims[2] << "]";
*rv = ss.str();
return;
}
case kMaxRegistersPerBlock: {
CUDA_CALL(cudaDeviceGetAttribute(&value, cudaDevAttrMaxRegistersPerBlock, dev.device_id));
break;
}
case kGcnArch:
return;
case kApiVersion: {
*rv = CUDA_VERSION;
return;
}
case kDriverVersion:
return;
}
*rv = value;
}
void* AllocDataSpace(Device dev, size_t nbytes, size_t alignment, DLDataType type_hint) final {
ICHECK_EQ(256 % alignment, 0U) << "CUDA space is aligned at 256 bytes";
void* ret;
if (dev.device_type == kDLCUDAHost) {
VLOG(1) << "allocating " << nbytes << "bytes on host";
CUDA_CALL(cudaMallocHost(&ret, nbytes));
} else {
CUDA_CALL(cudaSetDevice(dev.device_id));
size_t free_mem, total_mem;
CUDA_CALL(cudaMemGetInfo(&free_mem, &total_mem));
VLOG(1) << "allocating " << nbytes << " bytes on device, with " << free_mem
<< " bytes currently free out of " << total_mem << " bytes available";
CUDA_CALL(cudaMalloc(&ret, nbytes));
}
return ret;
}
void FreeDataSpace(Device dev, void* ptr) final {
if (dev.device_type == kDLCUDAHost) {
VLOG(1) << "freeing host memory";
CUDA_CALL(cudaFreeHost(ptr));
} else {
CUDA_CALL(cudaSetDevice(dev.device_id));
VLOG(1) << "freeing device memory";
CUDA_CALL(cudaFree(ptr));
}
}
protected:
void CopyDataFromTo(const void* from, size_t from_offset, void* to, size_t to_offset, size_t size,
Device dev_from, Device dev_to, DLDataType type_hint,
TVMStreamHandle stream) final {
cudaStream_t cu_stream = static_cast<cudaStream_t>(stream);
from = static_cast<const char*>(from) + from_offset;
to = static_cast<char*>(to) + to_offset;
if (dev_from.device_type == kDLCUDAHost) {
dev_from.device_type = kDLCPU;
}
if (dev_to.device_type == kDLCUDAHost) {
dev_to.device_type = kDLCPU;
}
// In case there is a copy from host mem to host mem */
if (dev_to.device_type == kDLCPU && dev_from.device_type == kDLCPU) {
memcpy(to, from, size);
return;
}
if (dev_from.device_type == kDLCUDA && dev_to.device_type == kDLCUDA) {
CUDA_CALL(cudaSetDevice(dev_from.device_id));
if (dev_from.device_id == dev_to.device_id) {
GPUCopy(from, to, size, cudaMemcpyDeviceToDevice, cu_stream);
} else {
cudaMemcpyPeerAsync(to, dev_to.device_id, from, dev_from.device_id, size, cu_stream);
}
} else if (dev_from.device_type == kDLCUDA && dev_to.device_type == kDLCPU) {
CUDA_CALL(cudaSetDevice(dev_from.device_id));
GPUCopy(from, to, size, cudaMemcpyDeviceToHost, cu_stream);
} else if (dev_from.device_type == kDLCPU && dev_to.device_type == kDLCUDA) {
CUDA_CALL(cudaSetDevice(dev_to.device_id));
GPUCopy(from, to, size, cudaMemcpyHostToDevice, cu_stream);
} else {
LOG(FATAL) << "expect copy from/to GPU or between GPU";
}
}
public:
TVMStreamHandle CreateStream(Device dev) {
CUDA_CALL(cudaSetDevice(dev.device_id));
cudaStream_t retval;
CUDA_CALL(cudaStreamCreate(&retval));
return static_cast<TVMStreamHandle>(retval);
}
void FreeStream(Device dev, TVMStreamHandle stream) {
CUDA_CALL(cudaSetDevice(dev.device_id));
cudaStream_t cu_stream = static_cast<cudaStream_t>(stream);
CUDA_CALL(cudaStreamDestroy(cu_stream));
}
void SyncStreamFromTo(Device dev, TVMStreamHandle event_src, TVMStreamHandle event_dst) {
CUDA_CALL(cudaSetDevice(dev.device_id));
cudaStream_t src_stream = static_cast<cudaStream_t>(event_src);
cudaStream_t dst_stream = static_cast<cudaStream_t>(event_dst);
cudaEvent_t evt;
CUDA_CALL(cudaEventCreate(&evt));
CUDA_CALL(cudaEventRecord(evt, src_stream));
CUDA_CALL(cudaStreamWaitEvent(dst_stream, evt, 0));
CUDA_CALL(cudaEventDestroy(evt));
}
void StreamSync(Device dev, TVMStreamHandle stream) final {
CUDA_CALL(cudaSetDevice(dev.device_id));
CUDA_CALL(cudaStreamSynchronize(static_cast<cudaStream_t>(stream)));
}
void SetStream(Device dev, TVMStreamHandle stream) final {
CUDAThreadEntry::ThreadLocal()->stream = static_cast<cudaStream_t>(stream);
}
void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final {
return CUDAThreadEntry::ThreadLocal()->pool.AllocWorkspace(dev, size);
}
void FreeWorkspace(Device dev, void* data) final {
CUDAThreadEntry::ThreadLocal()->pool.FreeWorkspace(dev, data);
}
static CUDADeviceAPI* Global() {
// NOTE: explicitly use new to avoid exit-time destruction of global state
// Global state will be recycled by OS as the process exits.
static auto* inst = new CUDADeviceAPI();
return inst;
}
private:
static void GPUCopy(const void* from, void* to, size_t size, cudaMemcpyKind kind,
cudaStream_t stream) {
if (stream != nullptr) {
CUDA_CALL(cudaMemcpyAsync(to, from, size, kind, stream));
} else {
CUDA_CALL(cudaMemcpy(to, from, size, kind));
}
}
};
typedef dmlc::ThreadLocalStore<CUDAThreadEntry> CUDAThreadStore;
CUDAThreadEntry::CUDAThreadEntry() : pool(kDLCUDA, CUDADeviceAPI::Global()) {}
CUDAThreadEntry* CUDAThreadEntry::ThreadLocal() { return CUDAThreadStore::Get(); }
TVM_REGISTER_GLOBAL("device_api.cuda").set_body([](TVMArgs args, TVMRetValue* rv) {
DeviceAPI* ptr = CUDADeviceAPI::Global();
*rv = static_cast<void*>(ptr);
});
TVM_REGISTER_GLOBAL("device_api.cuda_host").set_body([](TVMArgs args, TVMRetValue* rv) {
DeviceAPI* ptr = CUDADeviceAPI::Global();
*rv = static_cast<void*>(ptr);
});
class GPUTimerNode : public TimerNode {
public:
virtual void Start() {
CUDA_CALL(cudaEventRecord(start_, CUDAThreadEntry::ThreadLocal()->stream));
}
virtual void Stop() { CUDA_CALL(cudaEventRecord(stop_, CUDAThreadEntry::ThreadLocal()->stream)); }
virtual int64_t SyncAndGetElapsedNanos() {
CUDA_CALL(cudaEventSynchronize(stop_));
float milliseconds = 0;
CUDA_CALL(cudaEventElapsedTime(&milliseconds, start_, stop_));
return milliseconds * 1e6;
}
virtual ~GPUTimerNode() {
CUDA_CALL(cudaEventDestroy(start_));
CUDA_CALL(cudaEventDestroy(stop_));
}
GPUTimerNode() {
CUDA_CALL(cudaEventCreate(&start_));
CUDA_CALL(cudaEventCreate(&stop_));
}
static constexpr const char* _type_key = "GPUTimerNode";
TVM_DECLARE_FINAL_OBJECT_INFO(GPUTimerNode, TimerNode);
private:
cudaEvent_t start_;
cudaEvent_t stop_;
};
TVM_REGISTER_OBJECT_TYPE(GPUTimerNode);
TVM_REGISTER_GLOBAL("profiling.timer.gpu").set_body_typed([](Device dev) {
return Timer(make_object<GPUTimerNode>());
});
TVM_DLL String GetCudaFreeMemory() {
size_t free_mem, total_mem;
CUDA_CALL(cudaMemGetInfo(&free_mem, &total_mem));
std::stringstream ss;
ss << "Current CUDA memory is " << free_mem << " bytes free out of " << total_mem
<< " bytes on device";
return ss.str();
}
TVM_REGISTER_GLOBAL("runtime.GetCudaFreeMemory").set_body_typed(GetCudaFreeMemory);
} // namespace runtime
} // namespace tvm