22#include <cuda_runtime_api.h>
34namespace detail::cuda_reference
37enum class CopyDirection
44enum class CopyExecution
50template <
class Accessor>
struct IsConjugatedCudaAccessor : std::false_type
53template <
class Accessor>
54struct IsConjugatedCudaAccessor<
uni20::conjugated_accessor<Accessor>>
55 : std::bool_constant<RawCudaAccessorTraits<Accessor>::is_raw>
58template <
class Accessor>
struct IsConjugatedHostAccessor : std::false_type
61template <
class Accessor>
62struct IsConjugatedHostAccessor<
uni20::conjugated_accessor<Accessor>>
63 : std::bool_constant<uni20::is_default_accessor_v<Accessor>>
68template <
class Mdspan>
69inline constexpr bool is_conjugated_cuda_mdspan =
70 is_cuda_mdspan<Mdspan> && IsConjugatedCudaAccessor<typename std::remove_cvref_t<Mdspan>::accessor_type>
::value;
72template <
class Mdspan>
73inline constexpr bool is_supported_cuda_mdspan = is_raw_cuda_mdspec<Mdspan> || is_conjugated_cuda_mdspan<Mdspan>;
75template <
class Mdspan>
78template <
class Mdspan>
79inline constexpr bool is_conjugated_host_mdspan =
82template <
class Mdspan>
83inline constexpr bool is_supported_host_mdspan = is_raw_host_mdspan<Mdspan> || is_conjugated_host_mdspan<Mdspan>;
85template <
class OutputMdspan,
class InputMdspan>
86concept SupportedCopyMdspans =
90 (std::remove_cvref_t<OutputMdspan>::rank() == std::remove_cvref_t<InputMdspan>::rank()) &&
91 std::same_as<std::remove_cv_t<
typename std::remove_cvref_t<OutputMdspan>::element_type>,
92 std::remove_cv_t<
typename std::remove_cvref_t<InputMdspan>::element_type>> &&
93 ((is_raw_cuda_mdspec<OutputMdspan> &&
94 (is_supported_cuda_mdspan<InputMdspan> || is_supported_host_mdspan<InputMdspan>)) ||
95 (is_raw_host_mdspan<OutputMdspan> && is_supported_cuda_mdspan<InputMdspan>));
97template <
class Scalar>
struct CopyPlan
100 CopyDirection direction = CopyDirection::device_to_device;
101 CopyExecution execution = CopyExecution::runtime_copy;
102 ElementwiseCopyTransform transform = ElementwiseCopyTransform::identity;
105 Scalar* output_host =
nullptr;
106 Scalar
const* input_host =
nullptr;
107 std::size_t output_offset = 0;
108 std::size_t input_offset = 0;
109 std::size_t element_count = 0;
110 std::size_t output_span_size = 0;
111 std::size_t input_span_size = 0;
112 LoweredElementwiseCopyPlan elementwise_plan;
113 std::vector<Scalar> input_staging;
114 bool conjugate_host_output =
false;
115 bool has_work =
false;
118template <
class Mapping> [[nodiscard]]
bool mapping_is_contiguous(Mapping
const& mapping)
120 return mapping.is_unique() && mapping.is_exhaustive();
123template <
class Mdspan> [[nodiscard]]
bool active_strides_are_positive(Mdspan
const&
span)
125 for (std::size_t axis = 0; axis < std::remove_cvref_t<Mdspan>::rank(); ++axis)
127 if (
span.extent(axis) == 0)
return true;
129 for (std::size_t axis = 0; axis < std::remove_cvref_t<Mdspan>::rank(); ++axis)
131 if (
span.extent(axis) > 1 &&
span.stride(axis) <= 0)
return false;
136template <
class OutputMdspan,
class InputMdspan>
137[[nodiscard]]
bool physical_orders_match(OutputMdspan
const& output, InputMdspan
const& input)
139 for (std::size_t axis = 0; axis < std::remove_cvref_t<OutputMdspan>::rank(); ++axis)
141 if (output.extent(axis) > 1 && output.stride(axis) != input.stride(axis))
return false;
146template <
class OutputMdspan,
class InputMdspan>
147[[nodiscard]]
bool try_make_elementwise_plan(OutputMdspan
const& output, InputMdspan
const& input,
148 LoweredElementwiseCopyPlan& plan)
151 return try_lower_strided_elementwise_plan<elementwise_copy_maximum_rank>(host_plan, plan);
154template <
class Scalar>
157 CHECK(offset <= buffer.
size() && count <= buffer.
size() - offset, offset, count, buffer.
size());
160template <uni20::cuda::BufferMdspec Mdspan> [[nodiscard]]
auto cuda_buffer_view(Mdspan&
span)
162 return span.data_descriptor();
165template <
class OutputMdspan,
class InputMdspan>
166 requires SupportedCopyMdspans<OutputMdspan, InputMdspan>
167[[nodiscard]]
auto prepare_copy(OutputMdspan& output, InputMdspan& input)
169 using output_type = std::remove_cvref_t<OutputMdspan>;
170 using scalar_type = std::remove_cv_t<typename output_type::element_type>;
171 CopyPlan<scalar_type> plan;
173 for (std::size_t axis = 0; axis < output_type::rank(); ++axis)
175 if (output.extent(axis) != input.extent(axis))
182 if (!active_strides_are_positive(output) || !active_strides_are_positive(input))
188 auto const output_required_span = output.mapping().required_span_size();
189 auto const input_required_span = input.mapping().required_span_size();
190 if (std::cmp_less(output_required_span, 0) ||
191 std::cmp_greater(output_required_span, std::numeric_limits<std::size_t>::max()) ||
192 std::cmp_less(input_required_span, 0) ||
193 std::cmp_greater(input_required_span, std::numeric_limits<std::size_t>::max()))
198 plan.output_span_size =
static_cast<std::size_t
>(output_required_span);
199 plan.input_span_size =
static_cast<std::size_t
>(input_required_span);
201 if constexpr (is_cuda_mdspan<OutputMdspan> && is_cuda_mdspan<InputMdspan>)
203 plan.direction = CopyDirection::device_to_device;
205 else if constexpr (is_cuda_mdspan<OutputMdspan>)
207 plan.direction = CopyDirection::host_to_device;
211 plan.direction = CopyDirection::device_to_host;
214 bool const runtime_copy_eligible = mapping_is_contiguous(output.mapping()) &&
215 mapping_is_contiguous(input.mapping()) && physical_orders_match(output, input);
216 bool needs_elementwise_kernel =
false;
217 if (!runtime_copy_eligible)
219 if constexpr (is_cuda_mdspan<OutputMdspan> && is_cuda_mdspan<InputMdspan>)
220 needs_elementwise_kernel =
true;
228 if constexpr (is_conjugated_cuda_mdspan<InputMdspan>)
230 if (plan.direction == CopyDirection::device_to_device)
231 needs_elementwise_kernel =
true;
233 plan.conjugate_host_output =
true;
236 if (needs_elementwise_kernel)
238 if constexpr (!supports_elementwise_copy<scalar_type>)
243 if (!output.mapping().is_unique() || !try_make_elementwise_plan(output, input, plan.elementwise_plan))
248 plan.execution = CopyExecution::elementwise_kernel;
249 plan.element_count = plan.elementwise_plan.element_count();
250 if constexpr (is_conjugated_cuda_mdspan<InputMdspan>) plan.transform = ElementwiseCopyTransform::conjugate;
254 if (plan.output_span_size != plan.input_span_size)
259 plan.element_count = plan.output_span_size;
262 CHECK(plan.element_count <= std::numeric_limits<std::size_t>::max() /
sizeof(scalar_type), plan.element_count,
263 sizeof(scalar_type));
265 if constexpr (is_cuda_mdspan<OutputMdspan>)
267 auto output_handle = cuda_buffer_view(output);
268 plan.output_buffer = std::addressof(output_handle.buffer());
269 plan.output_offset = output_handle.element_offset();
270 validate_cuda_range(*plan.output_buffer, plan.output_offset, plan.output_span_size);
274 plan.output_host = output.data_handle();
275 CHECK(plan.element_count == 0 || plan.output_host !=
nullptr);
278 if constexpr (is_cuda_mdspan<InputMdspan>)
280 auto input_handle = cuda_buffer_view(input);
281 plan.input_buffer = std::addressof(input_handle.buffer());
282 plan.input_offset = input_handle.element_offset();
283 validate_cuda_range(*plan.input_buffer, plan.input_offset, plan.input_span_size);
287 plan.input_host = input.data_handle();
288 CHECK(plan.element_count == 0 || plan.input_host !=
nullptr);
291 if (plan.element_count == 0)
297 if constexpr (is_conjugated_host_mdspan<InputMdspan>)
299 CHECK(plan.direction == CopyDirection::host_to_device);
300 plan.input_staging.resize(plan.element_count);
301 for (std::size_t index = 0; index < plan.element_count; ++index)
302 plan.input_staging[index] =
uni20::conj(plan.input_host[index]);
303 plan.input_host = plan.input_staging.data();
306 if constexpr (is_cuda_mdspan<OutputMdspan> && is_cuda_mdspan<InputMdspan>)
308 if (plan.output_buffer == plan.input_buffer)
310 if (plan.output_offset == plan.input_offset && plan.execution == CopyExecution::runtime_copy)
317 if (plan.output_offset == plan.input_offset)
324 if (plan.execution == CopyExecution::elementwise_kernel &&
325 plan.output_buffer->device().ordinal() != plan.input_buffer->device().ordinal())
333 plan.has_work = plan.element_count != 0;
337template <
class Scalar>
void enqueue_device_copy(CopyPlan<Scalar>
const& plan,
uni20::cuda::Stream const& stream)
339 CHECK(plan.direction == CopyDirection::device_to_device && plan.output_buffer !=
nullptr &&
340 plan.input_buffer !=
nullptr);
343 int const output_device = plan.output_buffer->device().ordinal();
344 int const input_device = plan.input_buffer->device().ordinal();
347 auto enqueue = [&](Scalar* output_data, Scalar
const* input_data) {
348 if (plan.execution == CopyExecution::elementwise_kernel)
351 if constexpr (supports_elementwise_copy<Scalar>)
353 plan.elementwise_plan.visit([&](
auto const& elementwise_plan) {
354 enqueue_elementwise_copy(output_data, input_data, elementwise_plan, plan.transform,
stream.native_handle(),
361 PANIC(
"unsupported scalar reached CUDA reference elementwise copy");
365 std::size_t
const bytes = plan.element_count *
sizeof(Scalar);
366 if (output_device == input_device)
369 cudaMemcpyAsync(output_data, input_data,
bytes, cudaMemcpyDeviceToDevice,
stream.native_handle()),
370 "cudaMemcpyAsync device-to-device", output_device);
375 cudaMemcpyPeerAsync(output_data, output_device, input_data, input_device,
bytes,
stream.native_handle()),
376 "cudaMemcpyPeerAsync", output_device);
380 if (plan.output_buffer == plan.input_buffer)
384 auto access = plan.output_buffer->write_synchronized_with(stream);
385 enqueue(access.data() + plan.output_offset, access.data() + plan.input_offset);
389 auto output = plan.output_buffer->write_synchronized_with(stream);
390 auto input = plan.input_buffer->read_synchronized_with(stream);
391 enqueue(output.data() + plan.output_offset, input.data() + plan.input_offset);
393 completion.release(output);
394 completion.release(input);
397template <
class Scalar>
void execute_blocking_copy(CopyPlan<Scalar>
const& plan)
400 if (!plan.has_work)
return;
402 std::size_t
const bytes = plan.element_count *
sizeof(Scalar);
403 if (plan.direction == CopyDirection::device_to_device)
405 auto stream = plan.output_buffer->resources().streams().acquire();
406 enqueue_device_copy(plan, stream);
410 if (plan.direction == CopyDirection::host_to_device)
412 auto output = plan.output_buffer->blocking_write_access();
413 int const device = plan.output_buffer->device().ordinal();
415 uni20::cuda::check(cudaMemcpy(output.data() + plan.output_offset, plan.input_host,
bytes, cudaMemcpyHostToDevice),
416 "cudaMemcpy host-to-device", device);
426 uni20::cuda::detail::synchronize_after_failed_publication(
nullptr, device,
427 "publish pageable host-to-device completion");
434 auto input = plan.input_buffer->blocking_read_access();
435 int const device = plan.input_buffer->device().ordinal();
437 uni20::cuda::check(cudaMemcpy(plan.output_host, input.data() + plan.input_offset,
bytes, cudaMemcpyDeviceToHost),
438 "cudaMemcpy device-to-host", device);
440 if (plan.conjugate_host_output)
442 for (std::size_t index = 0; index < plan.element_count; ++index)
443 plan.output_host[index] =
uni20::conj(plan.output_host[index]);
448template <uni20::MutableMdspecLike OutputMdspan, uni20::MdspecLike InputMdspan>
consteval auto copy_acceptance()
450 if constexpr (SupportedCopyMdspans<OutputMdspan, InputMdspan>)
457template <
class OutputMdspan,
class InputMdspan>
458 requires SupportedCopyMdspans<OutputMdspan, InputMdspan>
461 auto preparation = prepare_copy(output, input);
463 execute_blocking_copy(preparation);
469template <uni20::MutableMdspecLike OutputMdspan, uni20::MdspecLike InputMdspan>
472 constexpr auto acceptance = detail::cuda_reference::copy_acceptance<OutputMdspan, InputMdspan>();
480template <uni20::MutableMdspecLike OutputMdspan, uni20::MdspecLike InputMdspan>
483 return detail::cuda_reference::copy(output, input);
Typed CUDA device buffers and scoped stream access guards.
One operation-tail completion shared by multiple CUDA buffer accesses.
Definition buffer.hpp:1160
static Completion record(int device, cudaStream_t stream)
Record a completion at the current tail of a native CUDA stream.
Definition runtime.cpp:312
Move-only logical CUDA buffer and completion state.
Definition buffer.hpp:222
std::size_t size() const noexcept
Return the number of typed elements in the allocation.
Definition buffer.hpp:260
Temporarily select one CUDA device and restore the previous device on destruction.
Definition runtime.hpp:44
Reference-counted lease of one CUDA stream-pool slot.
Definition runtime.hpp:65
An mdspan-like type whose accessor is stdex::default_accessor.
Definition mdspan/concepts.hpp:501
A readable type implementing the mdspan observers and multidimensional subscript contract.
Definition mdspan/concepts.hpp:291
MdspecLike types whose eventual storage supports writes.
Definition mdspan/concepts.hpp:398
An mdspec-like type whose mapping exposes multidimensional strides.
Definition mdspan/concepts.hpp:427
CUDA-accessible mdspec backed by a CudaBufferView descriptor.
Definition cuda_storage.hpp:118
Mdspan accessor adaptor for lazy complex conjugation.
Structured CUDA runtime failures and checked-call helpers.
CUDA Tensor storage with deferred device-memory data descriptors.
locale_t value
Definition data_table.cpp:49
Operation-tag backend dispatch over tensor-view operands.
Typed launch boundary for CUDA accessor-respecting element copies.
uni20::complex< T > conj(detail::standard_complex< T > x)
Returns the complex conjugate for complex-valued scalars.
Definition math.hpp:70
auto make_multi_iteration_plan(std::tuple< Mappings... > const &mappings)
Build a merged iteration plan for multiple tensors sharing the same extents.
Definition iteration_plan.hpp:263
Helpers for constructing merged iteration plans over strided tensors.
Scalar math helper utilities.
Mdspan concept and accessor extensions for Uni20.
Shared CUDA reference-backend traits for raw buffer mdspecs.
void check(cudaError_t status, std::string_view operation, std::optional< int > device, std::source_location where)
Raise when a CUDA runtime operation did not return cudaSuccess.
Definition cuda_error.cpp:44
stream
Selects the ordinary display stream for a message.
Definition display.hpp:25
void copy(std::span< Scalar > destination, std::span< Scalar const > source)
Copy a local dense vector.
Definition dense_linalg.hpp:183
Definition linalg/async.hpp:28
constexpr bool kernel_attempt_succeeded(KernelAttempt attempt) noexcept
Return whether a backend performed the operation.
Definition kernel_attempt.hpp:40
KernelAttempt try_kernel(BlasBackend, gemm_op const &, C &c, S const &alpha, A &a, B &b, S const &beta)
Pack logical host values and execute GEMM through value-preserving provider conversion.
Definition linalg/backends/blas/binary80.hpp:30
KernelAttempt
Result of one runtime kernel backend attempt.
Definition kernel_attempt.hpp:27
constexpr KernelAcceptance< KernelTypeAcceptance::no > kernel_types_no
Report that a backend rejects the argument types.
Definition dispatch_error.hpp:34
consteval auto kernel_accepts_types(BlasBackend, gemm_op const &, C &, S const &, A &, B &, S const &)
Accept host-readable native binary80 operands, including transforming accessors.
Definition linalg/backends/blas/binary80.hpp:19
constexpr KernelAcceptance< KernelTypeAcceptance::maybe > kernel_types_maybe
Report that runtime operand values determine backend acceptance.
Definition dispatch_error.hpp:37
Definition async/async.hpp:26
std::size_t bytes
Definition presentation.cpp:18
std::size_t span
Definition presentation.cpp:2017
Backend value for generic CUDA runtime kernels and transfers.
Definition backend_selector.hpp:48
Accessor-respecting element copy operation tag.
Definition operation_tags.hpp:33
Concepts for tensor-level objects that resolve mdspan kernel operands.
#define CHECK(cond,...)
Definition trace.hpp:299
#define PANIC(...)
Definition trace.hpp:382
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309