blob: f4fc89112345c26601bddd399606baa5be4260e9 [file]
#include "ATen/ATen.h"
#include "ATen/Context.h"
#include "ATen/Dispatch.h"
#include "ATen/NativeFunctions.h"
#include "ATen/cuda/CUDAApplyUtils.cuh"
#include "ATen/cuda/CUDAContext.h"
#include "ATen/cuda/CUDAEvent.h"
#include "ATen/cuda/CUDAStream.h"
#include "ATen/native/Copy.h"
namespace {
using namespace at;
using namespace at::cuda;
// Copy operator for the pointwise apply kernel
template <typename dst_T, typename src_T>
struct CopyOp {
static void apply(Tensor& dst, const Tensor& src) {
CUDA_tensor_apply2<dst_T, src_T>(
dst, src, [] __device__(dst_T & dst_val, const src_T& src_val) {
#if __CUDA_ARCH__ >= 350
dst_val = static_cast<dst_T>(
static_cast<native::inter_copy_type_t<dst_T>>(__ldg(&src_val)));
#else
dst_val = static_cast<dst_T>(static_cast<native::inter_copy_type_t<dst_T>>(src_val));
#endif
});
}
};
// device-to-device copy, does type conversion
template <typename dst_T, typename src_T>
void copy_device_to_device(Tensor& dst, const Tensor& src) {
auto numel = dst.numel();
if (dst.is_same(src) || numel == 0) {
return;
}
// We can memcpy the memory if:
// -both tensors are contiguous; or,
// -there is only one element to copy; or,
// -FIXME: if both tensors have matching size and stride arrays, and no
// holes within (in other words, there is some permutation that can be applied
// to the size/strides such that the resulting tensor is
// contiguous).
// -AND: both tensors have the same type.
bool same_type = std::is_same<dst_T, src_T>::value;
bool memcpy_eligible =
((src.is_contiguous() && dst.is_contiguous()) || (numel == 1)) &&
same_type;
Device src_device = src.device();
Device dst_device = dst.device();
CUDAGuard device_guard(src_device);
// Try to enable p2p access. This also handles the case src_device ==
// dst_device.
bool p2pEnabled = THCState_getPeerToPeerAccess(
globalContext().getTHCState(), src_device.index(), dst_device.index());
// We always perform the copy on the source device, using the
// current stream on the source device.
// If the copy is on the default stream, then we fully synchronize
// both src and dst's default streams for completion of the
// copy. We have to explicitly do this for non-contig copies.
// This mimics the behavior of cross-device cudaMemcpyAsync on
// the default stream.
// If the copy is not on the default stream, then it is up to the
// user to add needed synchronization on the dst device, since the
// stream on the dst device that wishes to synchronize may not be
// the same index as the one on the src device.
CUDAStream copy_stream = getCurrentCUDAStream(src_device.index());
if (src_device != dst_device && copy_stream == nullptr) {
// This is a cross-device copy on the default stream. We perform a
// two-way barrier between both devices' default streams before
// the copy. This ensures that any write-after-write and
// write-after-read dependencies on the destination side are
// handled, so that no one is operating on the dst memory when
// we perform the copy.
// src waits on dst barrier (src already waits on src)
CUDAEvent dst_ready;
device_guard.set_device(dst_device);
dst_ready.record(getDefaultCUDAStream(dst_device.index()));
device_guard.set_device(src_device);
dst_ready.block(copy_stream);
}
if (memcpy_eligible) {
// Perform the copy
AT_CUDA_CHECK(cudaMemcpyAsync(
dst.data<dst_T>(),
src.data<src_T>(),
numel * sizeof(dst_T),
cudaMemcpyDeviceToDevice,
copy_stream));
} else {
// Non-contiguous copy or a type-conversion copy
// We avoid creating temporary memory copies if possible.
// If both src and dst are on the same device, or if they are on
// different devices and p2p access is enabled, perform the copy
// by a pointwise copy kernel.
// Otherwise, we'll have to make contiguous (which will in fact
// invoke copy() again), and then perform the copy.
// FIXME: might want to consider only running the pointwise kernel
// if both src and dst innermost dimensions are contiguous. If
// they are not, then taking the hit of the memory allocation/free
// might be worth it to avoid non-coalesced reads or writes.
if (p2pEnabled) {
CopyOp<dst_T, src_T>::apply(dst, src);
} else {
// GPUs can't access each other directly, but the tensors
// involved are non-contiguous and/or are different types.
// Make sure the src is contiguous and in the same type as dst
Tensor src_contig;
if (same_type) {
src_contig = src.contiguous();
} else {
// Types are different
// Copy into the new format, contiguous, on the source device
src_contig = at::empty_like(dst, src.options().dtype(dst.dtype()));
CopyOp<dst_T, src_T>::apply(src_contig, src);
}
// Make sure the dst is contiguous
device_guard.set_device(dst_device);
Tensor dst_contig = dst.contiguous();
// Now, we are ready for a cross-device memcpy of contiguous
// data, of the same layout and type
device_guard.set_device(src_device);
AT_CUDA_CHECK(cudaMemcpyAsync(
dst_contig.data<dst_T>(),
src_contig.data<dst_T>(),
numel * sizeof(dst_T),
cudaMemcpyDeviceToDevice,
copy_stream));
if (!dst.is_contiguous()) {
copy_device_to_device<dst_T, dst_T>(dst, dst_contig);
}
}
}
if (src_device != dst_device && copy_stream == nullptr) {
// dst waits on src barrier (dst already waits on dst). We cannot
// operate on dst's copy until the copy is complete.
// Still on src_device, record default stream event
CUDAEvent src_ready;
src_ready.record(copy_stream);
device_guard.set_device(dst_device);
src_ready.block(getDefaultCUDAStream(dst_device.index()));
}
AT_CUDA_CHECK(cudaGetLastError());
}
void copy_from_cpu(Tensor& dst, const Tensor& src) {
Tensor dst_contig = dst.contiguous();
Tensor src_contig = src.contiguous();
CUDAStream stream = getCurrentCUDAStream();
AT_CUDA_CHECK(cudaMemcpyAsync(
dst_contig.data_ptr(),
src_contig.data_ptr(),
src.numel() * src.dtype().itemsize(),
cudaMemcpyHostToDevice,
stream));
AT_CUDA_CHECK(cudaStreamSynchronize(stream));
AT_DISPATCH_ALL_TYPES_AND_HALF(src.type(), "copy_from_cpu", [&]() {
copy_device_to_device<scalar_t, scalar_t>(dst, dst_contig);
});
}
void copy_to_cpu(Tensor& dst, const Tensor& src) {
Tensor dst_contig = dst.contiguous();
Tensor src_contig = src.contiguous();
CUDAGuard device_guard(src.device());
CUDAStream stream = getCurrentCUDAStream();
AT_CUDA_CHECK(cudaMemcpyAsync(
dst_contig.data_ptr(),
src_contig.data_ptr(),
src.numel() * src.dtype().itemsize(),
cudaMemcpyDeviceToHost,
stream));
AT_CUDA_CHECK(cudaStreamSynchronize(stream));
_copy_same_type_(dst, dst_contig);
}
void copy_from_cpu_async_(Tensor& dst, const Tensor& src) {
AT_CHECK(dst.is_contiguous(), "Target tensor must be contiguous.");
AT_CHECK(src.is_contiguous(), "Source tensor must be contiguous.");
if (dst.numel() == 0) {
return;
}
CUDAGuard device_guard(dst.device());
CUDAStream stream = getCurrentCUDAStream();
AT_DISPATCH_ALL_TYPES_AND_HALF(src.type(), "copy_from_cpu_async", [&]() {
AT_CUDA_CHECK(cudaMemcpyAsync(
dst.data<scalar_t>(),
src.data<scalar_t>(),
src.numel() * sizeof(scalar_t),
cudaMemcpyHostToDevice,
stream));
AT_CUDA_CHECK(THCCachingHostAllocator_recordEvent(
src.storage().data<scalar_t>(), stream));
});
}
void copy_to_cpu_async_(Tensor& dst, const Tensor& src) {
AT_CHECK(dst.is_contiguous(), "Target tensor must be contiguous.");
AT_CHECK(src.is_contiguous(), "Source tensor must be contiguous.");
if (dst.numel() == 0) {
return;
}
CUDAGuard device_guard(src.device());
CUDAStream stream = getCurrentCUDAStream();
AT_DISPATCH_ALL_TYPES_AND_HALF(src.type(), "copy_to_cpu_async", [&]() {
AT_CUDA_CHECK(cudaMemcpyAsync(
dst.data<scalar_t>(),
src.data<scalar_t>(),
src.numel() * sizeof(scalar_t),
cudaMemcpyDeviceToHost,
stream));
AT_CUDA_CHECK(THCCachingHostAllocator_recordEvent(
src.storage().data<scalar_t>(), stream));
});
}
template <typename dst_T>
void _copy__cuda(Tensor& dst, const Tensor& src, bool non_blocking) {
AT_CHECK(dst.numel() == src.numel(), "sizes do not match");
AT_DISPATCH_ALL_TYPES_AND_HALF(src.type(), "_copy__cuda", [&]() {
if (dst.is_cuda() && src.is_cuda()) {
copy_device_to_device<dst_T, scalar_t>(dst, src);
} else if (dst.is_cuda()) {
if (std::is_same<dst_T, scalar_t>::value) {
if (non_blocking) {
copy_from_cpu_async_(dst, src);
} else {
copy_from_cpu(dst, src);
}
} else {
// Do a dtype converting copy on the CPU, then copy to device
Tensor srcf = at::empty_like(src, src.options().dtype(dst.dtype()));
_copy_(srcf, src);
copy_from_cpu(dst, srcf);
}
} else {
if (std::is_same<dst_T, scalar_t>::value) {
if (non_blocking) {
copy_to_cpu_async_(dst, src);
} else {
copy_to_cpu(dst, src);
}
} else {
// Copy to CPU as the same dtype, then do a dtype converting copy
Tensor srcf = at::empty_like(src, dst.options().dtype(src.dtype()));
copy_to_cpu(srcf, src);
_copy_(dst, srcf);
}
}
});
}
} // namespace
namespace at {
namespace native {
Tensor& _copy__cuda(Tensor& self, const Tensor& src, bool non_blocking) {
AT_DISPATCH_ALL_TYPES_AND_HALF(self.type(), "_copy__cuda", [&]() {
::_copy__cuda<scalar_t>(self, src, non_blocking);
});
return self;
}
Tensor _copy_from_cuda(
const Tensor& self,
const Tensor& dst,
bool non_blocking) {
Tensor dst_ = dst;
_copy__cuda(dst_, self);
return dst;
}
} // namespace native
} // namespace at