31 static void check_nvrtc_error(nvrtcResult result,
32 const std::string &name) {
34 assert(result == NVRTC_SUCCESS && nvrtcGetErrorString(result));
44 static void check_error(CUresult result,
45 const std::string &name) {
48 cuGetErrorString(result, &error);
49 if (result != CUDA_SUCCESS) {
50 std::cerr << name <<
" " << std::string(error) << std::endl;
52 assert(result == CUDA_SUCCESS && error);
59 static CUresult cuda_init() {
60 const CUresult result = cuInit(0);
61 check_error(result,
"cuInit");
65 static const CUresult result = cuda_init();
73 template<jit::
float_scalar T,
bool SAFE_MATH=false>
83 std::unordered_map<graph::leaf_node<T, SAFE_MATH> *, CUdeviceptr> kernel_arguments;
84#ifdef USE_CUDA_TEXTURES
86 std::unordered_map<void *, CUtexObject> texture_arguments;
89 CUdeviceptr result_buffer;
91 CUdeviceptr offset_buffer;
101 void check_error_async(CUresult result,
102 const std::string &name) {
103 check_error(result, name);
105 std::string async_name = name +
"_async";
106 check_error(cuStreamSynchronize(stream), async_name);
126 check_error(cuDeviceGetCount(&count),
"cuDeviceGetCount");
142 cuda_context(
const size_t index) : result_buffer(0), module(0), offset_buffer(0) {
143 check_error(cuDeviceGet(&device, index),
"cuDeviceGet");
144 check_error(cuDevicePrimaryCtxRetain(&context, device),
"cuDevicePrimaryCtxRetain");
145 check_error(cuCtxSetCurrent(context),
"cuCtxSetCurrent");
146 check_error(cuCtxSetCacheConfig(CU_FUNC_CACHE_PREFER_L1),
"cuCtxSetCacheConfig");
147 check_error(cuStreamCreate(&stream, CU_STREAM_DEFAULT),
"cuStreamCreate");
149 CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY,
150 device),
"cuDeviceGetAttribute");
158 check_error(cuModuleUnload(module),
"cuModuleUnload");
162 for (
auto &[key, value] : kernel_arguments) {
163 check_error(cuMemFree(value),
"cuMemFree");
166#ifdef USE_CUDA_TEXTURES
167 for (
auto &[key, value] : texture_arguments) {
168 CUDA_RESOURCE_DESC resource;
169 check_error(cuTexObjectGetResourceDesc(&resource, value),
170 "cuTexObjectGetResourceDesc");
172 check_error(cuArrayDestroy(resource.res.array.hArray),
"cuArrayDestroy");
173 check_error(cuTexObjectDestroy(value),
"cuTexObjectDestroy");
178 check_error(cuMemFree(result_buffer),
"cuMemFree");
182 check_error(cuMemFree(offset_buffer),
"cuMemFree");
186 check_error(cuStreamDestroy(stream),
"cuStreamDestroy");
187 check_error(cuDevicePrimaryCtxRelease(device),
"cuDevicePrimaryCtxRelease");
198 std::vector<std::string> names,
199 const bool add_reduction=
false) {
201 names.push_back(
"max_reduction");
204 nvrtcProgram kernel_program;
205 check_nvrtc_error(nvrtcCreateProgram(&kernel_program,
206 kernel_source.c_str(),
207 NULL, 0, NULL, NULL),
208 "nvrtcCreateProgram");
210 for (std::string &name : names) {
211 check_nvrtc_error(nvrtcAddNameExpression(kernel_program,
213 "nvrtcAddNameExpression");
216 check_nvrtc_error(nvrtcAddNameExpression(kernel_program,
218 "nvrtcAddNameExpression");
221 std::ostringstream arch;
223 check_error(cuDeviceGetAttribute(&compute_version,
224 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
225 device),
"cuDeviceGetAttribute");
226 arch <<
"--gpu-architecture=compute_";
227 arch << compute_version;
229 std::cout <<
"CUDA GPU info." << std::endl;
230 std::cout <<
" Major compute capability : " << compute_version << std::endl;
233 check_error(cuDeviceGetAttribute(&value,
234 CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK,
235 device),
"cuDeviceGetAttribute");
237 std::cout <<
" Max shared memory : " << value << std::endl;
239 check_error(cuDeviceGetAttribute(&value,
240 CU_DEVICE_ATTRIBUTE_WARP_SIZE,
241 device),
"cuDeviceGetAttribute");
243 std::cout <<
" Warp size : " << value << std::endl;
246 check_error(cuDeviceGetAttribute(&compute_version,
247 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
248 device),
"cuDeviceGetAttribute");
249 arch << compute_version;
251 std::cout <<
" Minor compute capability : " << compute_version << std::endl;
254 char device_name[100];
255 check_error(cuDeviceGetName(device_name, 100, device),
"cuDeviceGetName");
257 std::cout <<
" Device name : " << device_name << std::endl;
260 const std::string temp = arch.str();
261 std::array<const char *, 8> options({
264 "--relocatable-device-code=false",
265 "--include-path=" CUDA_INCLUDE,
266 "--include-path=" HEADER_DIR,
267 "--extra-device-vectorization",
268 "--device-as-default-execution-space",
272 if (nvrtcCompileProgram(kernel_program, options.size(), options.data())) {
274 check_nvrtc_error(nvrtcGetProgramLogSize(kernel_program, &log_size),
275 "nvrtcGetProgramLogSize");
277 char *log =
static_cast<char *
> (malloc(log_size));
278 check_nvrtc_error(nvrtcGetProgramLog(kernel_program, log),
279 "nvrtcGetProgramLog");
280 std::cout << log << std::endl;
282 std::cout << kernel_source << std::endl;
285 check_error(cuDeviceGetAttribute(&compute_version,
286 CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY,
287 device),
"cuDeviceGetAttribute");
289 std::cout <<
" Managed Memory : " << compute_version << std::endl;
293 check_nvrtc_error(nvrtcGetPTXSize(kernel_program, &ptx_size),
296 char *ptx =
static_cast<char *
> (malloc(ptx_size));
297 check_nvrtc_error(nvrtcGetPTX(kernel_program, ptx),
"nvrtcGetPTX");
299 check_nvrtc_error(nvrtcDestroyProgram(&kernel_program),
300 "nvrtcDestroyProgram");
302 std::array<CUjit_option, 3> module_options = {
303 CU_JIT_MAX_REGISTERS,
305 CU_JIT_POSITION_INDEPENDENT_CODE
307 std::array<void *, 3> module_values = {
308 reinterpret_cast<void *
> (
MAX_REG),
309 reinterpret_cast<void *
> (1),
310 reinterpret_cast<void *
> (0)
313 check_error(cuModuleLoadDataEx(&module, ptx, module_options.size(),
314 module_options.data(),
315 module_values.data()),
"cuModuleLoadDataEx");
338 const size_t num_rays,
342 check_error(cuModuleGetFunction(&function, module, kernel_name.c_str()),
"cuModuleGetFunction");
344 std::vector<void *> buffers;
345 std::unordered_set<graph::leaf_node<T, SAFE_MATH> *> needed_buffers;
347 const size_t buffer_element_size =
sizeof(T);
348 for (
auto &input : inputs) {
349 if (!kernel_arguments.contains(input.get())) {
350 kernel_arguments.try_emplace(input.get());
351 check_error(cuMemAllocManaged(&kernel_arguments[input.get()],
352 input->size()*
sizeof(T),
353 CU_MEM_ATTACH_GLOBAL),
354 "cuMemAllocManaged");
355 check_error(cuMemcpyHtoD(kernel_arguments[input.get()],
357 input->size()*
sizeof(T)),
360 if (!needed_buffers.contains(input.get())) {
361 buffers.push_back(
reinterpret_cast<void *
> (&kernel_arguments[input.get()]));
362 needed_buffers.insert(input.get());
365 for (
auto &
output : outputs) {
367 if (!kernel_arguments.contains(
output.get())) {
368 kernel_arguments.try_emplace(
output.get());
369 check_error(cuMemAllocManaged(&kernel_arguments[
output.get()],
371 CU_MEM_ATTACH_GLOBAL),
372 "cuMemAllocManaged");
374 if (!needed_buffers.contains(
output.get())) {
375 buffers.push_back(
reinterpret_cast<void *
> (&kernel_arguments[
output.get()]));
376 needed_buffers.insert(
output.get());
380 for (
auto &atomic : atomics) {
381 if (!kernel_arguments.contains(atomic.get())) {
382 kernel_arguments.try_emplace(atomic.get());
383 check_error(cuMemAllocManaged(&kernel_arguments[atomic.get()],
384 atomic->size()*
sizeof(T),
385 CU_MEM_ATTACH_GLOBAL),
386 "cuMemAllocManaged");
387 check_error(cuMemcpyHtoD(kernel_arguments[atomic.get()],
389 atomic->size()*
sizeof(T)),
392 if (!needed_buffers.contains(atomic.get())) {
393 buffers.push_back(
reinterpret_cast<void *
> (&kernel_arguments[atomic.get()]));
394 needed_buffers.insert(atomic.get());
398 const size_t num_buffers = buffers.size();
400 if (!kernel_arguments.contains(state.get())) {
401 kernel_arguments.try_emplace(state.get());
402 check_error(cuMemAllocManaged(&kernel_arguments[state.get()],
403 state->get_size_bytes(),
404 CU_MEM_ATTACH_GLOBAL),
405 "cuMemAllocManaged");
406 check_error(cuMemAlloc(&offset_buffer,
sizeof(uint32_t)),
"cuMemAlloc");
407 check_error(cuMemcpyHtoD(kernel_arguments[state.get()],
409 state->get_size_bytes()),
412 buffers.push_back(
reinterpret_cast<void *
> (&kernel_arguments[state.get()]));
413 buffers.push_back(
reinterpret_cast<void *
> (&offset_buffer));
416#ifdef USE_CUDA_TEXTURES
417 for (
auto &[data, size] : tex1d_list) {
418 if (!texture_arguments.contains(data)) {
419 texture_arguments.try_emplace(data);
420 CUDA_RESOURCE_DESC resource_desc;
421 CUDA_TEXTURE_DESC texture_desc;
422 CUDA_ARRAY_DESCRIPTOR array_desc;
424 array_desc.Width = size;
425 array_desc.Height = 1;
427 memset(&resource_desc, 0,
sizeof(CUDA_RESOURCE_DESC));
428 memset(&texture_desc, 0,
sizeof(CUDA_TEXTURE_DESC));
430 resource_desc.resType = CU_RESOURCE_TYPE_ARRAY;
431 texture_desc.addressMode[0] = CU_TR_ADDRESS_MODE_BORDER;
432 texture_desc.addressMode[1] = CU_TR_ADDRESS_MODE_BORDER;
433 texture_desc.addressMode[2] = CU_TR_ADDRESS_MODE_BORDER;
435 array_desc.Format = CU_AD_FORMAT_FLOAT;
437 array_desc.NumChannels = 2;
439 array_desc.NumChannels = 1;
442 array_desc.Format = CU_AD_FORMAT_UNSIGNED_INT32;
444 array_desc.NumChannels = 4;
446 array_desc.NumChannels = 2;
449 check_error(cuArrayCreate(&resource_desc.res.array.hArray, &array_desc),
451 check_error(cuMemcpyHtoA(resource_desc.res.array.hArray, 0, data,
452 size*
sizeof(
float)*array_desc.NumChannels),
455 check_error(cuTexObjectCreate(&texture_arguments[data],
456 &resource_desc, &texture_desc,
458 "cuTexObjectCreate");
460 buffers.push_back(
reinterpret_cast<void *
> (&texture_arguments[data]));
462 for (
auto &[data, size] : tex2d_list) {
463 if (!texture_arguments.contains(data)) {
464 texture_arguments.try_emplace(data);
465 CUDA_RESOURCE_DESC resource_desc;
466 CUDA_TEXTURE_DESC texture_desc;
467 CUDA_ARRAY_DESCRIPTOR array_desc;
469 array_desc.Width = size[0];
470 array_desc.Height = size[1];
472 memset(&resource_desc, 0,
sizeof(CUDA_RESOURCE_DESC));
473 memset(&texture_desc, 0,
sizeof(CUDA_TEXTURE_DESC));
475 resource_desc.resType = CU_RESOURCE_TYPE_ARRAY;
476 texture_desc.addressMode[0] = CU_TR_ADDRESS_MODE_BORDER;
477 texture_desc.addressMode[1] = CU_TR_ADDRESS_MODE_BORDER;
478 texture_desc.addressMode[2] = CU_TR_ADDRESS_MODE_BORDER;
479 const size_t total = size[0]*size[1];
481 array_desc.Format = CU_AD_FORMAT_FLOAT;
483 array_desc.NumChannels = 2;
485 array_desc.NumChannels = 1;
488 array_desc.Format = CU_AD_FORMAT_UNSIGNED_INT32;
490 array_desc.NumChannels = 4;
492 array_desc.NumChannels = 2;
495 check_error(cuArrayCreate(&resource_desc.res.array.hArray, &array_desc),
498 CUDA_MEMCPY2D copy_desc;
499 memset(©_desc, 0,
sizeof(copy_desc));
501 copy_desc.srcPitch = size[0]*
sizeof(float)*array_desc.NumChannels;
502 copy_desc.srcMemoryType = CU_MEMORYTYPE_HOST;
503 copy_desc.srcHost = data;
505 copy_desc.dstMemoryType = CU_MEMORYTYPE_ARRAY;
506 copy_desc.dstArray = resource_desc.res.array.hArray;
508 copy_desc.WidthInBytes = copy_desc.srcPitch;
509 copy_desc.Height = size[0];
511 check_error(cuMemcpy2D(©_desc),
"cuMemcpy2D");
513 check_error(cuTexObjectCreate(&texture_arguments[data],
514 &resource_desc, &texture_desc,
516 "cuTexObjectCreate");
518 buffers.push_back(
reinterpret_cast<void *
> (&texture_arguments[data]));
523 check_error(cuFuncGetAttribute(&value, CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK,
524 function),
"cuFuncGetAttribute");
526 check_error(cuDeviceGetAttribute(&warp_size,
527 CU_DEVICE_ATTRIBUTE_WARP_SIZE,
528 device),
"cuDeviceGetAttribute");
530 unsigned int total_parallel = state.get() ? state->size() : num_rays;
531 unsigned int threads_per_group = total_parallel < 1024 ? warp_size : value;
532 unsigned int thread_groups = total_parallel/threads_per_group + (total_parallel%threads_per_group ? 1 : 0);
535 check_error(cuOccupancyMaxPotentialBlockSize(&min_grid, &value, function, 0, 0, 0),
536 "cuOccupancyMaxPotentialBlockSize");
539 std::cout <<
" Kernel name : " << kernel_name << std::endl;
540 std::cout <<
" Threads per group : " << threads_per_group << std::endl;
541 std::cout <<
" Number of groups : " << thread_groups << std::endl;
542 std::cout <<
" Total problem size : " << threads_per_group*thread_groups << std::endl;
543 std::cout <<
" Total parallel : " << total_parallel << std::endl;
544 std::cout <<
" Min grid size : " << min_grid << std::endl;
545 std::cout <<
" Suggested Block size : " << value << std::endl;
547#ifdef PROFILE_KERNELS
551 return [
this, num_rays, function, thread_groups, threads_per_group, buffers
552#ifdef PROFILE_KERNELS
556#ifdef PROFILE_KERNELS
557 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
559 }, &timer),
"cuLaunchHostFunc");
561 for (uint32_t i = 0, ie = threads_per_group*thread_groups; i < num_rays; i += ie) {
562 check_error_async(cuStreamWriteValue32(stream, offset_buffer, i,
563 CU_STREAM_WRITE_VALUE_DEFAULT),
564 "cuStreamWriteValue32");
565 check_error_async(cuLaunchKernel(function,
567 threads_per_group, 1, 1,
569 buffers.data(), NULL),
572#ifdef PROFILE_KERNELS
573 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
575 }, &timer),
"cuLaunchHostFunc");
579 return [
this, function, thread_groups, threads_per_group, buffers
580#ifdef PROFILE_KERNELS
584#ifdef PROFILE_KERNELS
585 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
587 }, &timer),
"cuLaunchHostFunc");
589 check_error_async(cuLaunchKernel(function, thread_groups, 1, 1,
590 threads_per_group, 1, 1, 0, stream,
591 buffers.data(), NULL),
593#ifdef PROFILE_KERNELS
594 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
596 }, &timer),
"cuLaunchHostFunc");
610 std::function<
void(
void)> run) {
611 check_error(cuMemAllocManaged(&result_buffer,
sizeof(T),
612 CU_MEM_ATTACH_GLOBAL),
613 "cuMemAllocManaged");
615 std::vector<void *> buffers;
617 buffers.push_back(
reinterpret_cast<void *
> (&kernel_arguments[argument.get()]));
618 buffers.push_back(
reinterpret_cast<void *
> (&result_buffer));
621 check_error(cuModuleGetFunction(&function, module,
"max_reduction"),
622 "cuModuleGetFunction");
626 check_error(cuOccupancyMaxPotentialBlockSize(&min_grid, &value, function, 0, 0, 0),
627 "cuOccupancyMaxPotentialBlockSize");
630 std::cout <<
" Kernel name : max_reduction" << std::endl;
631 std::cout <<
" Min grid size : " << min_grid << std::endl;
632 std::cout <<
" Suggested Block size : " << value << std::endl;
635 return [
this, function, run, buffers] ()
mutable {
637 check_error_async(cuLaunchKernel(function, 1, 1, 1,
638 1024, 1, 1, 0, stream,
639 buffers.data(), NULL),
643 return reinterpret_cast<T *
> (result_buffer)[0];
654 std::vector<CUdeviceptr> buffers;
655 for (
auto &input : inputs) {
656 if (!kernel_arguments.contains(input.get())) {
657 kernel_arguments.try_emplace(input.get());
658 check_error(cuMemAllocManaged(&kernel_arguments[input.get()],
659 input->size()*
sizeof(T),
660 CU_MEM_ATTACH_GLOBAL),
661 "cuMemAllocManaged");
662 check_error(cuMemcpyHtoD(kernel_arguments[input.get()],
664 input->size()*
sizeof(T)),
667 buffers.push_back(kernel_arguments[input.get()]);
670 std::vector<size_t> sizes;
671 for (CUdeviceptr &buffer : buffers) {
673 check_error(cuMemGetAddressRange(NULL, &size, buffer),
674 "cuMemGetAddressRange");
675 sizes.push_back(size);
677#ifdef PROFILE_KERNELS
680 return [
this, buffers, sizes
681#ifdef PROFILE_KERNELS
685#ifdef PROFILE_KERNELS
686 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
688 }, &timer),
"cuLaunchHostFunc");
690 for (
size_t i = 0, ie = buffers.size(); i < ie; i++) {
691 check_error_async(cuMemsetD8Async(buffers[i], 0, sizes[i],
695#ifdef PROFILE_KERNELS
696 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
698 }, &timer),
"cuLaunchHostFunc");
710 std::vector<CUdeviceptr> sources;
711 std::vector<CUdeviceptr> destinations;
713 for (
auto &[out, in] : setters) {
714 if (!kernel_arguments.contains(in.get())) {
715 kernel_arguments.try_emplace(in.get());
716 check_error(cuMemAllocManaged(&kernel_arguments[in.get()],
717 in->size()*
sizeof(T),
718 CU_MEM_ATTACH_GLOBAL),
719 "cuMemAllocManaged");
720 check_error(cuMemcpyHtoD(kernel_arguments[in.get()],
722 in->size()*
sizeof(T)),
725 destinations.push_back(kernel_arguments[in.get()]);
727 if (!kernel_arguments.contains(out.get())) {
728 kernel_arguments.try_emplace(out.get());
729 check_error(cuMemAllocManaged(&kernel_arguments[out.get()],
730 out->size()*
sizeof(T),
731 CU_MEM_ATTACH_GLOBAL),
732 "cuMemAllocManaged");
733 check_error(cuMemcpyHtoD(kernel_arguments[out.get()],
735 out->size()*
sizeof(T)),
738 sources.push_back(kernel_arguments[out.get()]);
741 std::vector<size_t> sizes;
742 for (CUdeviceptr &buffer : sources) {
744 check_error(cuMemGetAddressRange(NULL, &size, buffer),
745 "cuMemGetAddressRange");
746 sizes.push_back(size);
748#ifdef PROFILE_KERNELS
751 return [
this, sources, destinations, sizes
752#ifdef PROFILE_KERNELS
756#ifdef PROFILE_KERNELS
757 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
759 }, &timer),
"cuLaunchHostFunc");
761 for (
size_t i = 0, ie = sources.size(); i < ie; i++) {
762 check_error_async(cuMemcpyDtoDAsync(destinations[i],
765 "cuMemcpyDtoDAsync");
767#ifdef PROFILE_KERNELS
768 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
770 }, &timer),
"cuLaunchHostFunc");
779 check_error_async(cuStreamSynchronize(stream),
"cuStreamSynchronize");
780 check_error(cuCtxSynchronize(),
"cuCtxSynchronize");
789 std::function<void(
void)>
run_function(std::function<
void(
void)> callback) {
790#ifdef PROFILE_KERNELS
793 return [
this, callback
794#ifdef PROFILE_KERNELS
798#ifdef PROFILE_KERNELS
799 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
801 }, &timer),
"cuLaunchHostFunc");
803 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
804 reinterpret_cast<std::function<
void(
void)
> *> (arg)->
operator()();
805 }, &callback),
"cuLaunchHostFunc");
806#ifdef PROFILE_KERNELS
807 check_error_async(cuLaunchHostFunc(stream, [](
void *arg) {
809 }, &timer),
"cuLaunchHostFunc");
823 for (
auto &out : nodes) {
824 const T temp =
reinterpret_cast<T *
> (kernel_arguments[out.get()])[index];
826 std::cout << std::real(temp) <<
" " << std::imag(temp) <<
" ";
828 std::cout << temp <<
" ";
831 std::cout << std::endl;
844 return reinterpret_cast<T *
> (kernel_arguments[node.get()])[index];
856 check_error(cuMemGetAddressRange(NULL, &size, kernel_arguments[node.get()]),
"cuMemGetAddressRange");
857 check_error_async(cuMemcpyHtoDAsync(kernel_arguments[node.get()], source, size, stream),
"cuMemcpyHtoDAsync");
869 check_error(cuMemGetAddressRange(NULL, &size, kernel_arguments[node.get()]),
"cuMemGetAddressRange");
870 check_error_async(cuMemcpyDtoHAsync(destination, kernel_arguments[node.get()], size, stream),
"cuMemcpyDtoHAsync");
879 source_buffer <<
"typedef unsigned int uint32_t;" << std::endl
880 <<
"typedef unsigned short uint16_t;" << std::endl
881 <<
"typedef short int16_t;" << std::endl
882 <<
"typedef unsigned char uint8_t;" << std::endl
883 <<
"template<typename T, size_t S>" << std::endl
884 <<
"class array {" << std::endl
885 <<
"private:" << std::endl
886 <<
" T _buffer[S];" << std::endl
887 <<
"public:" << std::endl
888 <<
" T operator[] (const size_t index) const {" << std::endl
889 <<
" return _buffer[index];" << std::endl
891 <<
" T &operator[] (const size_t index) {" << std::endl
892 <<
" return _buffer[index];" << std::endl
894 <<
"};" << std::endl;
896 source_buffer <<
"#define CUDA_DEVICE_CODE" << std::endl
897 <<
"#define M_PI " << M_PI << std::endl
898 <<
"#include <cuda/std/complex>" << std::endl
899 <<
"#include <special_functions.hpp>" << std::endl;
900#ifdef USE_CUDA_TEXTURES
902 source_buffer <<
"static __inline__ __device__ complex<float> to_cmp_float(float2 p) {"
905 jit::add_type<T> (source_buffer);
906 source_buffer <<
" (p.x, p.y);" << std::endl
909 source_buffer <<
"static __inline__ __device__ complex<double> to_cmp_double(uint4 p) {"
912 jit::add_type<T> (source_buffer);
913 source_buffer <<
" (__hiloint2double(p.y, p.x), __hiloint2double(p.w, p.z));"
918 source_buffer <<
"static __inline__ __device__ double to_double(uint2 p) {"
920 <<
" return __hiloint2double(p.y, p.x);"
947 const std::string name,
953 const std::vector<bool> &is_constant,
960 const size_t iterations=1) {
961 source_buffer << std::endl;
962 source_buffer <<
"extern \"C\" __global__ void "
963 << name <<
"(" << std::endl;
965 int used_thread_mem = 0;
967 check_error(cuDeviceGetAttribute(&max_shared_mem,
968 CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK,
969 device),
"cuDeviceGetAttribute");
972 check_error(cuDeviceGetAttribute(&warp_size,
973 CU_DEVICE_ATTRIBUTE_WARP_SIZE,
974 device),
"cuDeviceGetAttribute");
978#ifdef USE_INPUT_CACHE
979 if (!is_constant[0] && iterations > 1) {
980 const size_t needed_mem = inputs[0]->size() > 1024 ?
983 if (used_thread_mem + needed_mem < max_shared_mem) {
984 used_thread_mem += needed_mem;
985 thread_shared.insert(inputs[0].get());
987 }
else if (is_constant[0] &&
988 inputs[0]->size() < size &&
989 inputs[0]->size() < 1024) {
990 const size_t needed_mem = inputs[0]->size()*
sizeof(T);
991 if (used_thread_mem + needed_mem < max_shared_mem) {
992 used_thread_mem += needed_mem;
993 thread_shared.insert(inputs[0].get());
994 thread_mem[inputs[0].get()] =
jit::to_string(
't', inputs[0].get());
998 source_buffer <<
" ";
999 if (is_constant[0]) {
1000 source_buffer <<
"const ";
1002 jit::add_type<T> (source_buffer);
1003 source_buffer <<
" * __restrict__ "
1005 used_args.insert(inputs[0].get());
1007 for (
size_t i = 1, ie = inputs.size(); i < ie; i++) {
1008#ifdef USE_INPUT_CACHE
1009 if (!is_constant[i] && iterations > 1) {
1010 const size_t needed_mem = inputs[i]->size() > 1024 ?
1012 warp_size*
sizeof(T);
1013 if (used_thread_mem + needed_mem < max_shared_mem) {
1014 used_thread_mem += needed_mem;
1015 thread_shared.insert(inputs[i].get());
1017 }
else if (is_constant[i] &&
1018 inputs[i]->size() < size &&
1019 inputs[i]->size() < 1024) {
1020 const size_t needed_mem = inputs[i]->size()*
sizeof(T);
1021 if (used_thread_mem + needed_mem < max_shared_mem) {
1022 used_thread_mem += needed_mem;
1023 thread_shared.insert(inputs[i].get());
1024 thread_mem[inputs[i].get()] =
jit::to_string(
't', inputs[i].get());
1028 if (!used_args.contains(inputs[i].get())) {
1029 inputs[i]->endline(source_buffer, usage,
',');
1030 source_buffer <<
" ";
1031 if (is_constant[i]) {
1032 source_buffer <<
"const ";
1034 jit::add_type<T> (source_buffer);
1035 source_buffer <<
" * __restrict__ "
1037 used_args.insert(inputs[i].get());
1040 for (
size_t i = 0, ie = outputs.size(); i < ie; i++) {
1041 if (!used_args.contains(outputs[i].get()) &&
1044 if (inputs.size()) {
1045 inputs[inputs.size() - 1]->endline(source_buffer,
1049 source_buffer <<
"," << std::endl;
1052 source_buffer <<
" ";
1053 jit::add_type<T> (source_buffer);
1054 source_buffer <<
" * __restrict__ "
1056 used_args.insert(outputs[i].get());
1059 for (
size_t i = 0, ie = atomics.size(); i < ie; i++) {
1060 if (!used_args.contains(atomics[i].get())) {
1062 if (outputs.size()) {
1063 outputs[outputs.size() - 1]->endline(source_buffer,
1065 }
else if (inputs.size()) {
1066 inputs[inputs.size() - 1]->endline(source_buffer,
1070 source_buffer <<
"," << std::endl;
1073 source_buffer <<
" ";
1074 jit::add_type<T> (source_buffer);
1075 source_buffer <<
" * __restrict__ "
1077 used_args.insert(atomics[i].get());
1081 source_buffer <<
"," << std::endl
1082 <<
" mt_state * __restrict__ "
1085 <<
" const uint32_t * __restrict__ offset"
1088#ifdef USE_CUDA_TEXTURES
1089 for (
auto &[key, value] : textures1d) {
1090 source_buffer <<
"," << std::endl;
1091 source_buffer <<
" cudaTextureObject_t "
1094 for (
auto &[key, value] : textures2d) {
1095 source_buffer <<
"," << std::endl;
1096 source_buffer <<
" cudaTextureObject_t "
1100 source_buffer <<
") {" << std::endl;
1101 if (thread_shared.size()) {
1102 source_buffer <<
" const int t_index = threadIdx.x;"
1105 source_buffer <<
" const int index = blockIdx.x*blockDim.x + threadIdx.x;"
1108 source_buffer <<
" if (";
1110 source_buffer <<
"offset[0] + ";
1112 source_buffer <<
"index < " << size <<
") {" << std::endl;
1114 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1115 if (is_constant[i]) {
1116#ifdef USE_INPUT_CACHE
1117 if (usage.at(inputs[i].get()) && inputs[i]->size() == size) {
1118 registers[inputs[i].get()] =
jit::to_string(
'r', inputs[i].get());
1119 source_buffer <<
" const ";
1120 jit::add_type<T> (source_buffer);
1121 source_buffer <<
" " << registers[inputs[i].get()] <<
" = "
1124 inputs[i]->endline(source_buffer, usage);
1127 registers[inputs[i].get()] =
jit::to_string(
'v', inputs[i].get())
1129 + (state.get() ?
"offset[0] + " :
"")
1135 if (thread_shared.size()) {
1136 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1137 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
1138 source_buffer <<
" __shared__ ";
1139 jit::add_type<T> (source_buffer);
1141 <<
"[" << inputs[i]->size() <<
"]";
1142 inputs[i]->endline(source_buffer, usage);
1145 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1146 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
1147 source_buffer <<
" for(int j = t_index; j < "
1148 << inputs[i]->size()
1149 <<
"; j += blockDim.x) {" << std::endl
1155 inputs[i]->endline(source_buffer, usage);
1156 source_buffer <<
" }" << std::endl;
1159 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1160 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
1161 source_buffer <<
" __syncthreads();"
1166 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1167 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
1168 thread_shared.erase(inputs[i].get());
1171 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1172 if (thread_shared.contains(inputs[i].get()) && !is_constant[i]) {
1173 source_buffer <<
" __shared__ ";
1174 jit::add_type<T> (source_buffer);
1177 << (inputs[i]->size() > 1024 ? 1024 : warp_size)
1179 inputs[i]->endline(source_buffer, usage);
1180 source_buffer <<
" "
1185 inputs[i]->endline(source_buffer, usage);
1190 if (iterations > 1) {
1191 source_buffer <<
" for (size_t j = 0; j < " << iterations <<
"; j++) {" << std::endl;
1194 for (
size_t i = 0, ie = inputs.size(); i < ie; i++) {
1195 if (!is_constant[i]) {
1196#ifdef USE_INPUT_CACHE
1197 if (usage.at(inputs[i].get())) {
1198 registers[inputs[i].get()] =
jit::to_string(
'r', inputs[i].get());
1199 source_buffer <<
" const ";
1200 jit::add_type<T> (source_buffer);
1201 source_buffer <<
" " << registers[inputs[i].get()] <<
" = ";
1202 if (thread_shared.contains(inputs[i].get())) {
1209 source_buffer <<
"offset[0] + ";
1211 source_buffer <<
"index]";
1213 inputs[i]->endline(source_buffer, usage);
1216 if (thread_shared.contains(inputs[i].get())) {
1217 registers[inputs[i].get()] =
jit::to_string(
't', inputs[i].get()) +
"[t_index]";
1219 registers[inputs[i].get()] =
jit::to_string(
'v', inputs[i].get()) +
"["
1220 + (state.get() ?
"offset[0] + " :
"")
1227#ifdef USE_INPUT_CACHE
1229 source_buffer <<
" mt_state &" << registers[state.get()] <<
" = "
1232 state->endline(source_buffer, usage);
1234 registers[state.get()] =
jit::to_string(
's', state.get()) +
"[threadIdx.x]";
1260 const size_t iterations=1) {
1261 std::unordered_set<void *> out_registers;
1262 for (
auto &[out, in] : setters) {
1263 if (!out->is_match(in)) {
1264 auto a = out->compile(source_buffer, registers,
1266 source_buffer <<
" ";
1267 if (thread_shared.contains(in.get())) {
1274 source_buffer <<
"offset[0] + ";
1276 source_buffer <<
"index] = ";
1278 if constexpr (SAFE_MATH) {
1280 jit::add_type<T> (source_buffer);
1281 source_buffer <<
" (";
1283 source_buffer <<
"isnan(real("
1284 << registers[a.get()]
1285 <<
")) ? 0.0 : real("
1286 << registers[a.get()]
1288 << registers[a.get()]
1289 <<
")) ? 0.0 : imag("
1290 << registers[a.get()]
1293 source_buffer << registers[a.get()];
1295 source_buffer <<
");" << std::endl;
1298 source_buffer <<
"isnan(" << registers[a.get()]
1301 source_buffer << registers[a.get()]
1302 <<
";" << std::endl;
1305 source_buffer << registers[a.get()] <<
";" << std::endl;
1307 out_registers.insert(out.get());
1311 for (
auto &out : outputs) {
1314 !out_registers.contains(out.get())) {
1315 auto a = out->compile(source_buffer, registers,
1317 source_buffer <<
" "
1321 source_buffer <<
"offset[0] + ";
1323 source_buffer <<
"index] = ";
1324 if constexpr (SAFE_MATH) {
1326 jit::add_type<T> (source_buffer);
1327 source_buffer <<
" (";
1329 source_buffer <<
"isnan(real("
1330 << registers[a.get()]
1331 <<
")) ? 0.0 : real("
1332 << registers[a.get()]
1334 << registers[a.get()]
1335 <<
")) ? 0.0 : imag("
1336 << registers[a.get()]
1339 source_buffer << registers[a.get()];
1341 source_buffer <<
");" << std::endl;
1344 source_buffer <<
"isnan(" << registers[a.get()]
1347 source_buffer << registers[a.get()]
1348 <<
";" << std::endl;
1351 source_buffer << registers[a.get()] <<
";" << std::endl;
1353 out_registers.insert(out.get());
1357 if (iterations > 1) {
1358 source_buffer <<
" }" << std::endl;
1360 for (
auto &[out, in] : setters) {
1361 if (thread_shared.contains(in.get())) {
1362 source_buffer <<
" "
1366 source_buffer <<
"offset[0] + ";
1368 source_buffer <<
"index] = "
1370 <<
"[t_index];" << std::endl;
1373 source_buffer <<
" }" << std::endl <<
"}" << std::endl;
1383 const size_t size) {
1384 source_buffer << std::endl;
1385 source_buffer <<
"extern \"C\" __global__ void max_reduction(" << std::endl;
1386 source_buffer <<
" const ";
1387 jit::add_type<T> (source_buffer);
1388 source_buffer <<
" * __restrict__ input," << std::endl;
1389 source_buffer <<
" ";
1390 jit::add_type<T> (source_buffer);
1391 source_buffer <<
" * __restrict__ result) {" << std::endl;
1392 source_buffer <<
" const unsigned int i = threadIdx.x;" << std::endl;
1393 source_buffer <<
" const unsigned int j = threadIdx.x/32;" << std::endl;
1394 source_buffer <<
" const unsigned int k = threadIdx.x%32;" << std::endl;
1395 source_buffer <<
" if (i < " << size <<
") {" << std::endl;
1396 source_buffer <<
" " << jit::type_to_string<T> () <<
" sub_max = ";
1398 source_buffer <<
"abs(input[i]);" << std::endl;
1400 source_buffer <<
"input[i];" << std::endl;
1402 source_buffer <<
" for (size_t index = i + 1024; index < " << size <<
"; index += 1024) {" << std::endl;
1404 source_buffer <<
" sub_max = max(sub_max, abs(input[index]));" << std::endl;
1406 source_buffer <<
" sub_max = max(sub_max, input[index]);" << std::endl;
1408 source_buffer <<
" }" << std::endl;
1409 source_buffer <<
" __shared__ " << jit::type_to_string<T> () <<
" thread_max[32];" << std::endl;
1410 source_buffer <<
" for (int index = 16; index > 0; index /= 2) {" << std::endl;
1411 source_buffer <<
" sub_max = max(sub_max, __shfl_down_sync(__activemask(), sub_max, index));" << std::endl;
1412 source_buffer <<
" }" << std::endl;
1413 source_buffer <<
" thread_max[j] = sub_max;" << std::endl;
1414 source_buffer <<
" __syncthreads();" << std::endl;
1415 source_buffer <<
" if (j == 0) {" << std::endl;
1416 source_buffer <<
" for (int index = 16; index > 0; index /= 2) {" << std::endl;
1417 source_buffer <<
" thread_max[k] = max(thread_max[k], __shfl_down_sync(__activemask(), thread_max[k], index));" << std::endl;
1418 source_buffer <<
" }" << std::endl;
1419 source_buffer <<
" *result = thread_max[0];" << std::endl;
1420 source_buffer <<
" }" << std::endl;
1421 source_buffer <<
" }" << std::endl;
1422 source_buffer <<
"}" << std::endl << std::endl;
1431 return reinterpret_cast<T *
> (kernel_arguments[node.get()]);
Class representing a cuda gpu context.
Definition cuda_context.hpp:74
T * get_buffer(graph::shared_leaf< T, SAFE_MATH > &node)
Get the buffer for a node.
Definition cuda_context.hpp:1430
~cuda_context()
Cuda context destructor.
Definition cuda_context.hpp:156
int remaining_const_memory
Remaining constant memory in bytes.
Definition cuda_context.hpp:117
std::function< void(void)> create_zero_call(graph::input_nodes< T, SAFE_MATH > &inputs)
Create kernel call that will be memset a buffer to zero.
Definition cuda_context.hpp:653
void copy_to_host(graph::shared_leaf< T, SAFE_MATH > node, T *destination)
Copy buffer contents to host.
Definition cuda_context.hpp:866
std::function< void(void)> create_copy_call(graph::copy_nodes< T, SAFE_MATH > &setters)
Create kernel call that will to copy one buffer to another.
Definition cuda_context.hpp:709
void create_reduction(std::ostringstream &source_buffer, const size_t size)
Create reduction.
Definition cuda_context.hpp:1382
std::function< void(void)> run_function(std::function< void(void)> callback)
Run a callback function in the queue.
Definition cuda_context.hpp:789
std::function< T(void)> create_max_call(graph::shared_leaf< T, SAFE_MATH > &argument, std::function< void(void)> run)
Create a max compute kernel calling function.
Definition cuda_context.hpp:609
void create_kernel_postfix(std::ostringstream &source_buffer, graph::output_nodes< T, SAFE_MATH > &outputs, graph::map_nodes< T, SAFE_MATH > &setters, graph::shared_random_state< T, SAFE_MATH > state, jit::register_map ®isters, const jit::register_usage &usage, const jit::argument_set &thread_shared, jit::register_map &thread_mem, const size_t iterations=1)
Create kernel postfix.
Definition cuda_context.hpp:1252
std::function< void(void)> create_kernel_call(const std::string kernel_name, graph::input_nodes< T, SAFE_MATH > inputs, graph::output_nodes< T, SAFE_MATH > outputs, graph::input_nodes< T, SAFE_MATH > atomics, graph::shared_random_state< T, SAFE_MATH > state, const size_t num_rays, const jit::texture1d_list &tex1d_list, const jit::texture2d_list &tex2d_list)
Create a kernel calling function.
Definition cuda_context.hpp:333
cuda_context(const size_t index)
Cuda context constructor.
Definition cuda_context.hpp:142
static size_t max_concurrency()
Get the maximum number of concurrent instances.
Definition cuda_context.hpp:124
void copy_to_device(graph::shared_leaf< T, SAFE_MATH > node, T *source)
Copy buffer contents to the device.
Definition cuda_context.hpp:853
static std::string device_type()
Device description.
Definition cuda_context.hpp:133
void create_kernel_prefix(std::ostringstream &source_buffer, const std::string name, graph::input_nodes< T, SAFE_MATH > &inputs, graph::output_nodes< T, SAFE_MATH > &outputs, graph::input_nodes< T, SAFE_MATH > atomics, graph::shared_random_state< T, SAFE_MATH > state, const size_t size, const std::vector< bool > &is_constant, jit::register_map ®isters, const jit::register_usage &usage, jit::texture1d_list &textures1d, jit::texture2d_list &textures2d, jit::argument_set &thread_shared, jit::register_map &thread_mem, const size_t iterations=1)
Create kernel prefix.
Definition cuda_context.hpp:946
void print_results(const size_t index, const graph::output_nodes< T, SAFE_MATH > &nodes)
Print out the results.
Definition cuda_context.hpp:820
static constexpr size_t random_state_size
Size of random state needed.
Definition cuda_context.hpp:114
static constexpr size_t random_state_scale
Random state size multiplyer.
Definition cuda_context.hpp:112
T check_value(const size_t index, const graph::shared_leaf< T, SAFE_MATH > &node)
Check the value.
Definition cuda_context.hpp:841
void wait()
Hold the current thread until the stream has completed.
Definition cuda_context.hpp:778
void create_header(std::ostringstream &source_buffer)
Create the source header.
Definition cuda_context.hpp:878
void compile(const std::string kernel_source, std::vector< std::string > names, const bool add_reduction=false)
Compile the kernels.
Definition cuda_context.hpp:197
A timing object.
Definition timing.hpp:18
Complex scalar concept.
Definition register.hpp:25
Double base concept.
Definition register.hpp:43
float base concept.
Definition register.hpp:38
#define MAX_REG
Maximum number of registers to use.
Definition cuda_context.hpp:22
subroutine assert(test, message)
Assert check.
Definition f_binding_test.f90:38
Name space for GPU backends.
Definition cpu_context.hpp:52
shared_atomic_accumulate_1D< T, SAFE_MATH > atomic_accumulate_1D_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a atomic accumulate 1D node.
Definition piecewise.hpp:2426
std::shared_ptr< random_state_node< T, SAFE_MATH > > shared_random_state
Convenience type alias for shared sqrt nodes.
Definition random.hpp:269
std::vector< shared_variable< T, SAFE_MATH > > input_nodes
Convenience type alias for a vector of inputs.
Definition node.hpp:2149
shared_variable< T, SAFE_MATH > variable_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a variable node.
Definition node.hpp:2169
std::vector< std::pair< shared_variable< T, SAFE_MATH >, shared_variable< T, SAFE_MATH > > > copy_nodes
Convenience type alias for copying buffers.
Definition node.hpp:2157
std::shared_ptr< leaf_node< T, SAFE_MATH > > shared_leaf
Convenience type alias for shared leaf nodes.
Definition node.hpp:691
std::vector< std::pair< shared_leaf< T, SAFE_MATH >, shared_variable< T, SAFE_MATH > > > map_nodes
Convenience type alias for mapping end codes back to inputs.
Definition node.hpp:2153
shared_random< T, SAFE_MATH > random_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a random node.
Definition random.hpp:539
std::vector< shared_leaf< T, SAFE_MATH > > output_nodes
Convenience type alias for a vector of output nodes.
Definition node.hpp:706
std::unordered_map< void *, size_t > register_usage
Type alias for counting register usage.
Definition register.hpp:261
std::unordered_map< void *, std::string > register_map
Type alias for mapping node pointers to register names.
Definition register.hpp:259
std::unordered_map< void *, std::array< size_t, 2 > > texture2d_list
Type alias for indexing 2D textures.
Definition register.hpp:267
std::unordered_map< void *, size_t > texture1d_list
Type alias for indexing 1D textures.
Definition register.hpp:265
std::string to_string(const char prefix, const NODE *pointer)
Convert a graph::leaf_node pointer to a string.
Definition register.hpp:247
std::unordered_set< void * > argument_set
Type for tacking thread shared memory.
Definition register.hpp:271
Name space for output files.
Definition output.hpp:16
Random constants and distributions.
Routines to time the ray execution.