From 915fd8df496dbb4a08b72f912c1947cba68a77f6 Mon Sep 17 00:00:00 2001 From: Anunalla Date: Sun, 22 Mar 2026 19:31:29 +0000 Subject: [PATCH 01/11] H100 new apps --- .gitmodules | 6 + README.md | 13 + get_data.sh | 12 + src/Makefile | 39 +- .../hw_def/common/gpuConfig.h | 4 +- .../ubench/mem/mem_bw/mem_bw.cu | 37 +- src/cuda/H100/Makefile | 88 + src/cuda/H100/README.md | 100 ++ src/cuda/H100/cufft/cufft_3d_c2c/Makefile | 9 + .../cufft_3d_c2c/cufft_3d_c2c_scalable.cu | 160 ++ .../H100/cufft/cufft_3d_c2c/cufft_utils.h | 64 + .../H100/cufft/cufft_lto_r2c_c2r/Makefile | 9 + .../cufft/cufft_lto_r2c_c2r/callback_params.h | 37 + .../H100/cufft/cufft_lto_r2c_c2r/common.cpp | 63 + .../H100/cufft/cufft_lto_r2c_c2r/common.h | 68 + .../cufft_lto_r2c_c2r_scalable.cpp | 181 ++ .../cufft/cufft_lto_r2c_c2r/nvrtc_helper.h | 102 ++ .../r2c_c2r_legacy_callback_example.cu | 148 ++ .../r2c_c2r_lto_callback_device.cu | 45 + .../r2c_c2r_lto_nvrtc_callback_example.cpp | 132 ++ .../cufft_lto_r2c_c2r/r2c_c2r_reference.cu | 86 + .../cufft_lto_r2c_c2r/r2c_c2r_reference.h | 26 + .../H100/cusolver/cusolver_Xgetrf/Makefile | 9 + .../cusolver_Xgetrf_scalable.cu | 268 +++ .../cusolver/cusolver_Xgetrf/cusolver_utils.h | 299 ++++ .../H100/cusolver/cusolver_ormqr/Makefile | 9 + .../cusolver_ormqr/cusolver_ormqr_scalable.cu | 235 +++ .../cusolver/cusolver_ormqr/cusolver_utils.h | 299 ++++ src/cuda/H100/external/cugraph | 1 + src/cuda/H100/external/newton | 1 + src/cuda/H100/generate_graph.py | 192 +++ src/cuda/H100/get_dwt_data.sh | 15 + src/cuda/H100/get_graph_data.sh | 30 + src/cuda/H100/get_image_data.sh | 30 + .../H100/graph/bfs_standalone/CMakeLists.txt | 41 + .../graph/bfs_standalone/bfs_standalone.cu | 259 +++ .../H100/graph/mst_standalone/CMakeLists.txt | 41 + .../graph/mst_standalone/mst_standalone.cu | 244 +++ src/cuda/H100/image/FDTD3d/FDTD3d.cpp | 232 +++ src/cuda/H100/image/FDTD3d/FDTD3d.h | 54 + src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu | 260 +++ src/cuda/H100/image/FDTD3d/FDTD3dGPU.h | 56 + .../H100/image/FDTD3d/FDTD3dGPUKernel.cuh | 160 ++ .../H100/image/FDTD3d/FDTD3dReference.cpp | 191 +++ src/cuda/H100/image/FDTD3d/FDTD3dReference.h | 59 + src/cuda/H100/image/FDTD3d/Makefile | 8 + src/cuda/H100/image/FDTD3d/exception.h | 151 ++ src/cuda/H100/image/FDTD3d/helper_cuda.h | 988 +++++++++++ src/cuda/H100/image/FDTD3d/helper_functions.h | 59 + src/cuda/H100/image/FDTD3d/helper_image.h | 1001 +++++++++++ src/cuda/H100/image/FDTD3d/helper_string.h | 428 +++++ src/cuda/H100/image/FDTD3d/helper_timer.h | 465 ++++++ src/cuda/H100/image/dwtHaar1D/Makefile | 8 + src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu | 386 +++++ .../H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh | 248 +++ src/cuda/H100/image/dwtHaar1D/exception.h | 151 ++ src/cuda/H100/image/dwtHaar1D/helper_cuda.h | 988 +++++++++++ .../H100/image/dwtHaar1D/helper_functions.h | 59 + src/cuda/H100/image/dwtHaar1D/helper_image.h | 1001 +++++++++++ src/cuda/H100/image/dwtHaar1D/helper_string.h | 428 +++++ src/cuda/H100/image/dwtHaar1D/helper_timer.h | 465 ++++++ .../H100/image/recursiveGaussian/Makefile | 12 + .../H100/image/recursiveGaussian/exception.h | 151 ++ .../image/recursiveGaussian/helper_cuda.h | 988 +++++++++++ .../recursiveGaussian/helper_functions.h | 59 + .../H100/image/recursiveGaussian/helper_gl.h | 267 +++ .../image/recursiveGaussian/helper_image.h | 1001 +++++++++++ .../image/recursiveGaussian/helper_math.h | 1469 +++++++++++++++++ .../image/recursiveGaussian/helper_string.h | 428 +++++ .../image/recursiveGaussian/helper_timer.h | 465 ++++++ .../recursiveGaussian/recursiveGaussian.cpp | 587 +++++++ .../recursiveGaussian_cuda.cu | 155 ++ .../recursiveGaussian_kernel.cuh | 235 +++ src/cuda/H100/newton/newton_diffsim_ball | 8 + src/cuda/H100/newton/newton_mpm_granular | 8 + src/cuda/H100/newton/newton_robot_cartpole | 8 + src/cuda/H100/newton/setup_newton.sh | 29 + src/cuda/H100/setup_vpi.sh | 82 + .../vpi_background_subtractor/CMakeLists.txt | 12 + .../vpi/vpi_background_subtractor/main.cpp | 219 +++ .../vpi_orb_feature_detector/CMakeLists.txt | 11 + .../vpi/vpi_orb_feature_detector/main.cpp | 289 ++++ .../vpi/vpi_stereo_disparity/CMakeLists.txt | 11 + .../H100/vpi/vpi_stereo_disparity/main.cpp | 354 ++++ src/cuda/cutlass-bench | 2 +- src/cuda/pytorch_examples | 2 +- src/setup_environment | 7 + 87 files changed, 18093 insertions(+), 14 deletions(-) create mode 100644 src/cuda/H100/Makefile create mode 100644 src/cuda/H100/README.md create mode 100644 src/cuda/H100/cufft/cufft_3d_c2c/Makefile create mode 100644 src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu create mode 100644 src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/callback_params.h create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.cpp create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_nvrtc_callback_example.cpp create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.cu create mode 100644 src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.h create mode 100644 src/cuda/H100/cusolver/cusolver_Xgetrf/Makefile create mode 100644 src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu create mode 100644 src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_utils.h create mode 100644 src/cuda/H100/cusolver/cusolver_ormqr/Makefile create mode 100644 src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu create mode 100644 src/cuda/H100/cusolver/cusolver_ormqr/cusolver_utils.h create mode 160000 src/cuda/H100/external/cugraph create mode 160000 src/cuda/H100/external/newton create mode 100755 src/cuda/H100/generate_graph.py create mode 100755 src/cuda/H100/get_dwt_data.sh create mode 100755 src/cuda/H100/get_graph_data.sh create mode 100755 src/cuda/H100/get_image_data.sh create mode 100644 src/cuda/H100/graph/bfs_standalone/CMakeLists.txt create mode 100644 src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu create mode 100644 src/cuda/H100/graph/mst_standalone/CMakeLists.txt create mode 100644 src/cuda/H100/graph/mst_standalone/mst_standalone.cu create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3d.cpp create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3d.h create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3dGPU.h create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp create mode 100644 src/cuda/H100/image/FDTD3d/FDTD3dReference.h create mode 100644 src/cuda/H100/image/FDTD3d/Makefile create mode 100644 src/cuda/H100/image/FDTD3d/exception.h create mode 100644 src/cuda/H100/image/FDTD3d/helper_cuda.h create mode 100644 src/cuda/H100/image/FDTD3d/helper_functions.h create mode 100644 src/cuda/H100/image/FDTD3d/helper_image.h create mode 100644 src/cuda/H100/image/FDTD3d/helper_string.h create mode 100644 src/cuda/H100/image/FDTD3d/helper_timer.h create mode 100644 src/cuda/H100/image/dwtHaar1D/Makefile create mode 100644 src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu create mode 100644 src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh create mode 100644 src/cuda/H100/image/dwtHaar1D/exception.h create mode 100644 src/cuda/H100/image/dwtHaar1D/helper_cuda.h create mode 100644 src/cuda/H100/image/dwtHaar1D/helper_functions.h create mode 100644 src/cuda/H100/image/dwtHaar1D/helper_image.h create mode 100644 src/cuda/H100/image/dwtHaar1D/helper_string.h create mode 100644 src/cuda/H100/image/dwtHaar1D/helper_timer.h create mode 100644 src/cuda/H100/image/recursiveGaussian/Makefile create mode 100644 src/cuda/H100/image/recursiveGaussian/exception.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_cuda.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_functions.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_gl.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_image.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_math.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_string.h create mode 100644 src/cuda/H100/image/recursiveGaussian/helper_timer.h create mode 100644 src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp create mode 100644 src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu create mode 100644 src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh create mode 100755 src/cuda/H100/newton/newton_diffsim_ball create mode 100755 src/cuda/H100/newton/newton_mpm_granular create mode 100755 src/cuda/H100/newton/newton_robot_cartpole create mode 100755 src/cuda/H100/newton/setup_newton.sh create mode 100755 src/cuda/H100/setup_vpi.sh create mode 100644 src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt create mode 100644 src/cuda/H100/vpi/vpi_background_subtractor/main.cpp create mode 100644 src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt create mode 100644 src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp create mode 100644 src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt create mode 100644 src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp diff --git a/.gitmodules b/.gitmodules index a26c652f2..7120e2048 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,9 @@ path = src/cuda/pytorch_examples url = https://github.com/accel-sim/pytorch_examples.git branch = inference_accelsim_v2 +[submodule "src/cuda/H100/external/cugraph"] + path = src/cuda/H100/external/cugraph + url = https://github.com/rapidsai/cugraph.git +[submodule "src/cuda/H100/external/newton"] + path = src/cuda/H100/external/newton + url = https://github.com/newton-physics/newton.git diff --git a/README.md b/README.md index 09db7bfda..f09602671 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,16 @@ To compile everything above for AccelWattch: ``` make accelwattch -C ./src ``` + +## H100 Benchmark Suite + +The H100 suite contains 15 modern GPU workloads from H100 profiling and analysis: + +- **cuFFT** (2 apps): FFT operations using cuFFT library +- **cuSolver** (2 apps): Linear algebra using cuSolver library +- **Image Processing** (3 apps): Wavelet transform, Gaussian filter, FDTD3d +- **Graph Algorithms** (2 apps): BFS and MST using cuGraph (git submodule) +- **Physics Simulation** (3 apps): Newton physics engine benchmarks (git submodule) +- **Computer Vision** (3 apps): VPI-based vision processing (optional, requires VPI 4.0) + +See [src/cuda/H100/README.md](src/cuda/H100/README.md) for details. diff --git a/get_data.sh b/get_data.sh index 77ce877ae..37d91b308 100755 --- a/get_data.sh +++ b/get_data.sh @@ -10,3 +10,15 @@ if [ ! -d $DATA_ROOT ]; then tar xzvf all.gpgpu-sim-app-data.tgz -C $BASH_ROOT rm all.gpgpu-sim-app-data.tgz fi + +# Generate H100 benchmark data +echo "Generating H100 benchmark data..." +if [ -f $BASH_ROOT/src/cuda/H100/get_graph_data.sh ]; then + bash $BASH_ROOT/src/cuda/H100/get_graph_data.sh || echo "Warning: Graph data generation failed" +fi +if [ -f $BASH_ROOT/src/cuda/H100/get_image_data.sh ]; then + bash $BASH_ROOT/src/cuda/H100/get_image_data.sh || echo "Warning: Image data generation failed" +fi +if [ -f $BASH_ROOT/src/cuda/H100/get_dwt_data.sh ]; then + bash $BASH_ROOT/src/cuda/H100/get_dwt_data.sh || echo "Warning: DWT data generation failed" +fi diff --git a/src/Makefile b/src/Makefile index a890d58f7..0c1db7882 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,7 +4,7 @@ $(error You must run "source setup_environment before calling make") endif ifeq ($(CUDA_GT_7), 1) -all: GPU_Microbenchmark microbench rodinia_2.0-ft cutlass rodinia-3.1 pannotia proxy-apps ispass-2009 lonestargpu-2.0 polybench custom_apps heterosync cuda_samples mlperf_inference vllm huggingface # mlperf_training +all: GPU_Microbenchmark microbench rodinia_2.0-ft cutlass rodinia-3.1 pannotia proxy-apps ispass-2009 lonestargpu-2.0 polybench custom_apps heterosync cuda_samples mlperf_inference vllm huggingface H100 # mlperf_training else ifeq ($(CUDA_GT_4), 1) all: pannotia rodinia_2.0-ft proxy-apps dragon-naive microbench rodinia-3.1 ispass-2009 dragon-cdp lonestargpu-2.0 polybench parboil shoc custom_apps @@ -19,7 +19,7 @@ accelwattch_hw_power: rodinia-3.1_hw_power parboil_hw_power cuda_samples-11.0_hw #Disable clean for now, It has a bug! # clean_dragon-naive clean_pannotia clean_proxy-apps -clean: clean_mlperf_inference clean_rodinia_2.0-ft clean_dragon-cdp clean_ispass-2009 clean_lonestargpu-2.0 clean_custom_apps clean_parboil clean_cutlass clean_rodinia-3.1 clean_heterosync clean_UVMSmart_test clean_cuda_samples clean_huggingface clean_GPU_Microbenchmark +clean: clean_mlperf_inference clean_rodinia_2.0-ft clean_dragon-cdp clean_ispass-2009 clean_lonestargpu-2.0 clean_custom_apps clean_parboil clean_cutlass clean_rodinia-3.1 clean_heterosync clean_UVMSmart_test clean_cuda_samples clean_huggingface clean_GPU_Microbenchmark clean_H100 clean_accelwattch: clean_rodinia-3.1 clean_parboil clean_cutlass clean_cuda_samples-11.0 clean_cuda_samples_hw_power clean_rodinia-3.1_hw_power clean_parboil_hw_power clean_accelwattch_ubench clean_data: @@ -543,6 +543,41 @@ clean_heterosync: clean_cutlass: rm -rf cuda/cutlass-bench/build +############################################################################### +# H100 Benchmarks - Modern GPU workloads (CUDA 11+, sm_75+) +############################################################################### +H100: + mkdir -p $(BINDIR)/$(BINSUBDIR)/ + # Initialize and update submodules (like cutlass pattern) + git submodule init && git submodule update + # Build simple apps (cuFFT, cuSolver, image processing) + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 simple + mv cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable $(BINDIR)/$(BINSUBDIR)/H100-cufft_3d_c2c + mv cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable $(BINDIR)/$(BINSUBDIR)/H100-cufft_lto_r2c_c2r + mv cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_ormqr + mv cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_Xgetrf + mv cuda/H100/image/dwtHaar1D/dwtHaar1D $(BINDIR)/$(BINSUBDIR)/H100-dwtHaar1D + mv cuda/H100/image/FDTD3d/FDTD3d $(BINDIR)/$(BINSUBDIR)/H100-FDTD3d + # Build graph apps (cuGraph submodule) + -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 graph && \ + cp cuda/H100/graph/bfs_standalone/build/bfs_standalone $(BINDIR)/$(BINSUBDIR)/H100-bfs && \ + cp cuda/H100/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/H100-mst + # Build Newton apps (Newton submodule) + cp -r cuda/H100/newton $(BINDIR)/$(BINSUBDIR)/ + bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh + chmod u+x $(BINDIR)/$(BINSUBDIR)/newton/newton_* + # Build VPI apps (if VPI installed) + @if [ -n "$(VPI_INSTALL_PATH)" ]; then \ + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 vpi && \ + find cuda/H100/vpi/*/build -name "vpi_*" -type f -executable -exec cp {} $(BINDIR)/$(BINSUBDIR)/ \;; \ + fi + +clean_H100: + $(SETENV) $(MAKE) -C cuda/H100 clean + rm -rf cuda/H100/external/cugraph/build + rm -rf cuda/H100/external/newton/build + rm -rf cuda/H100/newton/newton_venv + # clean_deeplearning: # $(SETENV) $(MAKE) $(MAKE_ARGS) noinline=$(noinline) -C cuda/cudnn/mnist clean diff --git a/src/cuda/GPU_Microbenchmark/hw_def/common/gpuConfig.h b/src/cuda/GPU_Microbenchmark/hw_def/common/gpuConfig.h index 0b0306596..4def21d8a 100644 --- a/src/cuda/GPU_Microbenchmark/hw_def/common/gpuConfig.h +++ b/src/cuda/GPU_Microbenchmark/hw_def/common/gpuConfig.h @@ -108,7 +108,9 @@ inline void parseGpuConfigArgs(int argc, char *argv[]) ++i; } config.MAX_WARPS_PER_SM = config.MAX_THREADS_PER_SM / config.WARP_SIZE; - config.MEM_CLK_FREQUENCY = config.MEM_CLK_FREQUENCY * 1e-3f; + // Note: MEM_CLK_FREQUENCY is already in MHz from initializeDeviceProp (line 313) + // Do not convert to GHz - the bandwidth calculation expects MHz + // config.MEM_CLK_FREQUENCY = config.MEM_CLK_FREQUENCY * 1e-3f; config.BLOCKS_PER_SM = config.MAX_THREADS_PER_SM / config.THREADS_PER_BLOCK; config.THREADS_PER_SM = config.BLOCKS_PER_SM * config.THREADS_PER_BLOCK; config.TOTAL_THREADS = config.THREADS_PER_BLOCK * config.BLOCKS_NUM; diff --git a/src/cuda/GPU_Microbenchmark/ubench/mem/mem_bw/mem_bw.cu b/src/cuda/GPU_Microbenchmark/ubench/mem/mem_bw/mem_bw.cu index 841bdee17..78c130e22 100644 --- a/src/cuda/GPU_Microbenchmark/ubench/mem/mem_bw/mem_bw.cu +++ b/src/cuda/GPU_Microbenchmark/ubench/mem/mem_bw/mem_bw.cu @@ -145,13 +145,32 @@ int main(int argc, char *argv[]) float milliseconds = 0; cudaEventElapsedTime(&milliseconds, start, stop); - unsigned N = ARRAY_SIZE * 6 * sizeof(float); // 6 arrays of floats types - float max_bw = (float)config.MEM_BITWIDTH * config.MEM_CLK_FREQUENCY * 2 / 1e3 / 8; - mem_bw = (float)(N) / ((float)(stopClk[0] - startClk[0])); - printf("Mem BW= %f (Byte/Clk)\n", mem_bw); - printf("Mem BW= %f (GB/sec)\n", (float)N / milliseconds / 1e6); - printf("Max Theortical Mem BW= %f (GB/sec)\n", max_bw); - printf("Mem Efficiency = %f %%\n", (mem_bw / max_bw) * 100); - - printf("Total Clk number = %u \n", stopClk[0] - startClk[0]); + // Find min and max clocks across all threads to get actual kernel execution time + uint32_t minStart = startClk[0], maxStop = stopClk[0]; + for (unsigned i = 1; i < config.TOTAL_THREADS; i++) { + if (startClk[i] < minStart) minStart = startClk[i]; + if (stopClk[i] > maxStop) maxStop = stopClk[i]; + } + uint32_t totalClocks = maxStop - minStart; + + unsigned N = ARRAY_SIZE * 6 * sizeof(float); // 6 arrays of floats (5 reads + 1 write) + + // Theoretical max bandwidth in GB/s + // MEM_CLK_FREQUENCY is in MHz, MEM_BITWIDTH is in bits + // BW = (Bus Width / 8 bytes) × (Clock MHz) × 2 (DDR) / 1000 = GB/s + float max_bw = (float)config.MEM_BITWIDTH / 8 * config.MEM_CLK_FREQUENCY * 2 / 1000; + + // Achieved bandwidth from CUDA event timing (most accurate) + float achieved_bw_from_time = (float)N / milliseconds / 1e6; + + // Achieved bandwidth from cycle count (less accurate, single SM perspective) + float achieved_bw_from_cycles = ((float)N / totalClocks) * config.CLK_FREQUENCY / 1e3; + + printf("Mem BW= %f (Byte/Clk)\n", (float)N / totalClocks); + printf("Mem BW (from time)= %f (GB/sec)\n", achieved_bw_from_time); + printf("Mem BW (from cycles)= %f (GB/sec)\n", achieved_bw_from_cycles); + printf("Max Theoretical Mem BW= %f (GB/sec)\n", max_bw); + printf("Mem Efficiency = %f %%\n", (achieved_bw_from_time / max_bw) * 100); + + printf("Total Clk number = %u (min start: %u, max stop: %u)\n", totalClocks, minStart, maxStop); } diff --git a/src/cuda/H100/Makefile b/src/cuda/H100/Makefile new file mode 100644 index 000000000..847c75ba9 --- /dev/null +++ b/src/cuda/H100/Makefile @@ -0,0 +1,88 @@ +# H100 Benchmark Suite Makefile (following cutlass-bench pattern) +.PHONY: all simple graph newton vpi clean + +# Default: Build all apps (submodules initialized by parent Makefile) +all: simple graph newton vpi + +# Simple apps (cuFFT, cuSolver, image processing - CUDA toolkit only) +simple: cufft cusolver image + +cufft: + $(MAKE) -C cufft/cufft_3d_c2c + $(MAKE) -C cufft/cufft_lto_r2c_c2r + +cusolver: + $(MAKE) -C cusolver/cusolver_ormqr + $(MAKE) -C cusolver/cusolver_Xgetrf + +image: + $(MAKE) -C image/dwtHaar1D + $(MAKE) -C image/recursiveGaussian + $(MAKE) -C image/FDTD3d + +# Graph apps (cuGraph submodule - built with CMake) +graph: + @echo "Building cuGraph library from submodule..." + @if [ -d "external/cugraph" ]; then \ + cd external/cugraph && \ + ./build.sh libcugraph --skip_cpp_tests -n && \ + echo "Building graph apps..." && \ + cd ../.. && \ + export CUGRAPH_ROOT="$(shell pwd)/external/cugraph" && \ + mkdir -p graph/bfs_standalone/build && \ + cd graph/bfs_standalone/build && \ + cmake .. -DCMAKE_CUDA_COMPILER=$(CUDA_INSTALL_PATH)/bin/nvcc && \ + $(MAKE) && \ + cd ../../.. && \ + mkdir -p graph/mst_standalone/build && \ + cd graph/mst_standalone/build && \ + cmake .. -DCMAKE_CUDA_COMPILER=$(CUDA_INSTALL_PATH)/bin/nvcc && \ + $(MAKE); \ + else \ + echo "WARNING: cuGraph submodule not found - skipping graph apps"; \ + fi + +# Newton apps (Newton submodule - Python-based) +newton: + @echo "Setting up Newton apps..." + chmod +x newton/setup_newton.sh newton/newton_diffsim_ball newton/newton_robot_cartpole newton/newton_mpm_granular + @echo "Newton wrapper scripts ready. Virtual environment will be created on first run." + +# VPI apps (VPI library from system - optional) +# NOTE: VPI requires system installation. Run setup_vpi.sh with sudo if VPI is not installed. +vpi: + @if command -v vpi-config &> /dev/null; then \ + echo "Building VPI apps..."; \ + mkdir -p vpi/vpi_background_subtractor/build && \ + cd vpi/vpi_background_subtractor/build && \ + cmake .. && $(MAKE) && \ + cd ../../.. && \ + mkdir -p vpi/vpi_orb_feature_detector/build && \ + cd vpi/vpi_orb_feature_detector/build && \ + cmake .. && $(MAKE) && \ + cd ../../.. && \ + mkdir -p vpi/vpi_stereo_disparity/build && \ + cd vpi/vpi_stereo_disparity/build && \ + cmake .. && $(MAKE); \ + else \ + echo "WARNING: VPI not installed - skipping VPI apps"; \ + echo " To install VPI, run: sudo bash setup_vpi.sh"; \ + fi + +clean: + # Clean simple apps + -$(MAKE) -C cufft/cufft_3d_c2c clean + -$(MAKE) -C cufft/cufft_lto_r2c_c2r clean + -$(MAKE) -C cusolver/cusolver_ormqr clean + -$(MAKE) -C cusolver/cusolver_Xgetrf clean + -$(MAKE) -C image/dwtHaar1D clean + -$(MAKE) -C image/recursiveGaussian clean + -$(MAKE) -C image/FDTD3d clean + # Clean graph apps and cuGraph build + -rm -rf graph/bfs_standalone/build + -rm -rf graph/mst_standalone/build + -rm -rf external/cugraph/cpp/build + # Clean Newton + -rm -rf newton/newton_venv + # Clean VPI apps + -rm -rf vpi/*/build diff --git a/src/cuda/H100/README.md b/src/cuda/H100/README.md new file mode 100644 index 000000000..3c19ec6e6 --- /dev/null +++ b/src/cuda/H100/README.md @@ -0,0 +1,100 @@ +# H100 Benchmark Suite + +14 modern GPU workloads from H100 profiling and analysis. + +## Applications + +### cuFFT (2 apps) - FFT operations +- `cufft_3d_c2c_scalable` - 3D Complex-to-Complex FFT +- `cufft_lto_r2c_c2r_scalable` - Real↔Complex FFT with LTO callbacks + +### cuSolver (2 apps) - Linear algebra +- `cusolver_ormqr_scalable` - QR factorization +- `cusolver_Xgetrf_scalable` - LU factorization + +### Image Processing (3 apps) +- `dwtHaar1D` - Haar wavelet transform +- `recursiveGaussian` - Recursive Gaussian filter +- `FDTD3d` - Finite-Difference Time-Domain 3D simulation + +### Graph Algorithms (2 apps) +- `bfs_standalone` - Breadth-First Search (requires cuGraph submodule) +- `mst_standalone` - Minimum Spanning Tree (requires cuGraph submodule) + +### Physics Simulation (3 apps) +- `newton_diffsim_ball` - Differential simulation (requires Newton submodule) +- `newton_robot_cartpole` - Robotics simulation (requires Newton submodule) +- `newton_mpm_granular` - Material Point Method simulation (requires Newton submodule) + +### Computer Vision (3 apps) +- `vpi_background_subtractor` - Background subtraction (requires VPI 4.0) +- `vpi_orb_feature_detector` - ORB feature detection (requires VPI 4.0) +- `vpi_stereo_disparity` - Stereo disparity calculation (requires VPI 4.0) + +## Dependencies + +- **CUDA 11.0+** - Required (provides cuFFT, cuSolver, cuBLAS libraries) +- **cuGraph** - Git submodule (auto-initialized for graph apps) +- **Newton** - Git submodule (auto-initialized for physics apps) +- **VPI 4.0** - install from https://developer.nvidia.com/embedded/vpi + +## Build + +```bash +# From repository root +source src/setup_environment + +# Generate data files (standard workflow) +make data + +# Build all H100 apps +make -C src H100 + +# Or build everything with: +make all -i -j -C src +``` + +Binaries are output to `bin//release/H100-*` + +Newton apps are copied to `bin//release/newton/newton_*` + +## Running + +```bash +# cuFFT apps +bin/*/release/H100-cufft_3d_c2c small +bin/*/release/H100-cufft_lto_r2c_c2r medium + +# cuSolver apps +bin/*/release/H100-cusolver_ormqr large +bin/*/release/H100-cusolver_Xgetrf medium + +# Image apps +bin/*/release/H100-dwtHaar1D +bin/*/release/H100-recursiveGaussian +bin/*/release/H100-FDTD3d + +# Graph apps (with generated data) +bin/*/release/H100-bfs data_dirs/cuda/H100/graph/karate.mtx +bin/*/release/H100-mst data_dirs/cuda/H100/graph/netscience.mtx + +# Newton apps +bin/*/release/newton/newton_diffsim_ball +bin/*/release/newton/newton_robot_cartpole +bin/*/release/newton/newton_mpm_granular + +# VPI apps (if VPI installed) +bin/*/release/vpi_background_subtractor cuda +``` + +## GPU Support + +- Requires compute capability 7.5+ (Turing, Ampere, Hopper) +- Tested on: V100 (sm_70), A100 (sm_80), H100 (sm_90) + +## Notes + +- Simple apps (cuFFT, cuSolver, image) build on any system with CUDA 11+ +- Graph apps require cuGraph submodule (automatically handled by build system) +- Newton apps create Python virtual environment on first run +- VPI apps are optional and only build if VPI library is installed diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/Makefile b/src/cuda/H100/cufft/cufft_3d_c2c/Makefile new file mode 100644 index 000000000..23ec8b328 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_3d_c2c/Makefile @@ -0,0 +1,9 @@ +EXECUTABLE := cufft_3d_c2c_scalable +CUFILES := cufft_3d_c2c_scalable.cu +CCFILES := +ADDITIONAL_LIBS := -lcufft +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +include ../../../common/common.mk diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu b/src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu new file mode 100644 index 000000000..87e2e6023 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2020 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cufft_utils.h" + +using dim_t = std::array; + +int main(int argc, char *argv[]) { + cufftHandle plan; + cudaStream_t stream = NULL; + + // Parse size argument (small/medium/large) + int n = 16; // Default: small + int batch_size = 4; + const char* size_name = "small"; + + if (argc > 1) { + if (strcmp(argv[1], "small") == 0) { + n = 16; // 16×16×16 = 4K elements + batch_size = 4; + size_name = "small"; + } else if (strcmp(argv[1], "medium") == 0) { + n = 32; // 32×32×32 = 32K elements + batch_size = 8; + size_name = "medium"; + } else if (strcmp(argv[1], "large") == 0) { + n = 64; // 64×64×64 = 262K elements + batch_size = 8; + size_name = "large"; + } + } + + dim_t fft = {n, n, n}; + int fft_size = fft[0] * fft[1] * fft[2]; + + std::printf("==============================================\n"); + std::printf("cuFFT 3D C2C Example (Scalable)\n"); + std::printf("==============================================\n"); + std::printf("Size: %s (%dx%dx%d)\n", size_name, n, n, n); + std::printf("FFT size: %d\n", fft_size); + std::printf("Batch size: %d\n", batch_size); + std::printf("==============================================\n\n"); + + using scalar_type = float; + using data_type = std::complex; + + std::vector data(fft_size * batch_size); + + // Initialize with simple pattern + std::random_device rd; + std::mt19937 gen(42); // Fixed seed for reproducibility + std::uniform_real_distribution dist(0.0f, 1.0f); + + for (int i = 0; i < data.size(); i++) { + data[i] = data_type(dist(gen), dist(gen)); + } + + if (n <= 16) { + std::printf("Input array (first 8 elements):\n"); + for (int i = 0; i < std::min(8, (int)data.size()); i++) { + std::printf("%f + %fj\n", data[i].real(), data[i].imag()); + } + std::printf("=====\n"); + } + + cufftComplex *d_data = nullptr; + + // inembed/onembed being nullptr indicates contiguous data for each batch, then the stride and dist settings are ignored + CUFFT_CALL(cufftPlanMany(&plan, fft.size(), fft.data(), + nullptr, 1, 0, // *inembed, istride, idist + nullptr, 1, 0, // *onembed, ostride, odist + CUFFT_C2C, batch_size)); + + CUDA_RT_CALL(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking)); + CUFFT_CALL(cufftSetStream(plan, stream)); + + // Create device data arrays + CUDA_RT_CALL(cudaMalloc(reinterpret_cast(&d_data), sizeof(data_type) * data.size())); + CUDA_RT_CALL(cudaMemcpyAsync(d_data, data.data(), sizeof(data_type) * data.size(), + cudaMemcpyHostToDevice, stream)); + + /* + * Note: + * Identical pointers to data and output arrays implies in-place transformation + */ + std::printf("Executing forward FFT...\n"); + CUFFT_CALL(cufftExecC2C(plan, d_data, d_data, CUFFT_FORWARD)); + CUDA_RT_CALL(cudaStreamSynchronize(stream)); + std::printf("Forward FFT complete.\n"); + + if (n <= 16) { + CUDA_RT_CALL(cudaMemcpyAsync(data.data(), d_data, sizeof(data_type) * data.size(), + cudaMemcpyDeviceToHost, stream)); + CUDA_RT_CALL(cudaStreamSynchronize(stream)); + std::printf("Output after Forward (first 8 elements):\n"); + for (int i = 0; i < std::min(8, (int)data.size()); i++) { + std::printf("%f + %fj\n", data[i].real(), data[i].imag()); + } + std::printf("=====\n"); + } + + // Normalize the data and inverse FFT + std::printf("Executing inverse FFT...\n"); + scaling_kernel<<<(data.size() + 127) / 128, 128, 0, stream>>>(d_data, data.size(), 1.f/fft_size); + CUFFT_CALL(cufftExecC2C(plan, d_data, d_data, CUFFT_INVERSE)); + CUDA_RT_CALL(cudaStreamSynchronize(stream)); + std::printf("Inverse FFT complete.\n"); + + if (n <= 16) { + CUDA_RT_CALL(cudaMemcpyAsync(data.data(), d_data, sizeof(data_type) * data.size(), + cudaMemcpyDeviceToHost, stream)); + CUDA_RT_CALL(cudaStreamSynchronize(stream)); + std::printf("Output after Inverse (first 8 elements):\n"); + for (int i = 0; i < std::min(8, (int)data.size()); i++) { + std::printf("%f + %fj\n", data[i].real(), data[i].imag()); + } + std::printf("=====\n"); + } + + + + /* free resources */ + CUDA_RT_CALL(cudaFree(d_data)); + + CUFFT_CALL(cufftDestroy(plan)); + + CUDA_RT_CALL(cudaStreamDestroy(stream)); + + CUDA_RT_CALL(cudaDeviceReset()); + + std::printf("\n==============================================\n"); + std::printf("SUCCESS: 3D C2C FFT completed\n"); + std::printf("==============================================\n"); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h b/src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h new file mode 100644 index 000000000..846d63ab7 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2020 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#pragma once + +// CUDA API error checking +#ifndef CUDA_RT_CALL +#define CUDA_RT_CALL( call ) \ + { \ + auto status = static_cast( call ); \ + if ( status != cudaSuccess ) \ + fprintf( stderr, \ + "ERROR: CUDA RT call \"%s\" in line %d of file %s failed " \ + "with " \ + "%s (%d).\n", \ + #call, \ + __LINE__, \ + __FILE__, \ + cudaGetErrorString( status ), \ + status ); \ + } +#endif // CUDA_RT_CALL + +// cufft API error chekcing +#ifndef CUFFT_CALL +#define CUFFT_CALL( call ) \ + { \ + auto status = static_cast( call ); \ + if ( status != CUFFT_SUCCESS ) \ + fprintf( stderr, \ + "ERROR: CUFFT call \"%s\" in line %d of file %s failed " \ + "with " \ + "code (%d).\n", \ + #call, \ + __LINE__, \ + __FILE__, \ + status ); \ + } +#endif // CUFFT_CALL + +__global__ +void scaling_kernel(cufftComplex* data, int element_count, float scale) { + const int tid = threadIdx.x + blockIdx.x * blockDim.x; + const int stride = blockDim.x * gridDim.x; + for (auto i = tid; i +#include "common.h" + +// Wave parameters +constexpr unsigned waves = 12; +constexpr float signal_max_A = 20.; +constexpr float signal_max_f = 500.; +constexpr float sampling_dt = 1e-3; + +// Initialize the input signal as a composite of sine waves +// with random amplitudes and frequencies +void init_input_signals(unsigned batches, unsigned signal_size, float* signals) { + std::mt19937 e2(0); + + std::uniform_real_distribution<> A_dist(0., signal_max_A); + std::uniform_real_distribution<> f_dist(0., signal_max_f); + + const unsigned complex_signal_size = signal_size / 2 + 1; + + for(unsigned batch = 0; batch < batches; ++batch) { + std::vector wave_amplitudes; + std::vector wave_frequencies; + + // Generate the amplitudes and frequencies of the waves + for(unsigned i = 0; i < waves; ++i) { + wave_amplitudes.push_back(A_dist(e2)); + wave_frequencies.push_back(f_dist(e2)); + } + + // Compose the signal + float time = 0.; + for(unsigned s = 0; s < signal_size; ++s) { + for(unsigned i = 0; i < waves; ++i) { + unsigned idx = batch * (2 * complex_signal_size) + s; + signals[idx] += wave_amplitudes[i] * sin(2. * PI * wave_frequencies[i] * time) ; + } + time += sampling_dt; + } + } +} + +#endif // _COMMON__CPP_ \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h new file mode 100644 index 000000000..6bcbacccc --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#ifndef _COMMON__H_ +#define _COMMON__H_ + +#include +#include +#include + +// Some helper definitions +#define ERROR_VALUE -1 +#define PASS_VALUE 0 +#define PI 3.1415926535897932 + +// Check CUDA API error +inline int checkErrors(cudaError_t error, int line_number) { + if (error != cudaSuccess) { + printf("Example failed in CUDA API on line %d with error %d\n", line_number, error); + return ERROR_VALUE; + } + return PASS_VALUE; +} + +// Check cuFFT API error +inline int checkErrors(cufftResult error, int line_number) { + if (error != CUFFT_SUCCESS) { + printf("Example failed in cuFFT API on line %d with error %d\n", line_number, error); + return ERROR_VALUE; + } + return PASS_VALUE; +} + +#define CHECK_ERROR(error) checkErrors(error, __LINE__) + +template +double compute_error(T* ref, T* out, unsigned batches, unsigned signal_size){ + double squared_diff = 0; + double squared_norm = 0; + const unsigned batch_offset = 2 * (signal_size / 2 + 1); + for (int b = 0; b < batches; b++) { + for (int i = 0; i < signal_size; i++) { + unsigned ref_idx = b * batch_offset + i; + squared_diff += std::norm(ref[ref_idx] - out[ref_idx]); // Note that std::norm(z) = z * conj(z), not the usual sqrt(z * conj(z)) + squared_norm += std::norm(ref[ref_idx]); + } + } + return std::sqrt(squared_diff / squared_norm); +} + +// Initialize input signals +void init_input_signals(unsigned batches, unsigned signal_size, float* signals); +#endif // _COMMON__H_ \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp new file mode 100644 index 000000000..a38ec3b25 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + + +/* + * Example showing the use of LTO callbacks with CUFFT to perform + * truncation with zero padding. + * +*/ + +#include +#include +#include +#include "r2c_c2r_reference.h" +#include "common.h" +#include "callback_params.h" + +// NOTE: Header containing the compiled LTO callback device function in a C array, generated with bin2c +#include "r2c_c2r_lto_callback_device_fatbin.h" + +// Note: Removed static_assert since window_size and signal_size are now runtime variables + +int test_r2c_window_c2r() { + + // Padded array for in-place transforms + float input_signals[batches][2 * complex_signal_size] = {}; + float output_signals[batches][2 * complex_signal_size]; + float reference[batches][2 * complex_signal_size]; + + init_input_signals(batches, signal_size, &input_signals[0][0]); + + const size_t complex_size_bytes = batches * complex_signal_size * 2 * sizeof(float); + + // Allocate and copy input from host to GPU + float *device_signals; + CHECK_ERROR(cudaMalloc((void **)&device_signals, complex_size_bytes)); + CHECK_ERROR(cudaMemcpy(device_signals, input_signals, complex_size_bytes, cudaMemcpyHostToDevice)); + + // Create a CUFFT plan for the forward transform, and a cuFFT plan for the inverse transform with load callback + cufftHandle forward_plan, inverse_plan_cb; + size_t work_size; + + CHECK_ERROR(cufftCreate(&forward_plan)); + CHECK_ERROR(cufftCreate(&inverse_plan_cb)); + + // NOTE: LTO callbacks must be set before plan creation and cannot be unset (yet) +#ifdef CB_USE_CONSTANT_MEMORY + cb_params *device_params = nullptr; + std::string callback_name = "windowing_constant_memory_callback"; +#else + // Define a structure used to pass in the window size + cb_params host_params; + host_params.window_size = window_size; + host_params.signal_size = complex_signal_size; + + // Allocate and copy callback parameters from host to GPU + cb_params *device_params; + CHECK_ERROR(cudaMalloc((void **)&device_params, sizeof(cb_params))); + CHECK_ERROR(cudaMemcpy(device_params, &host_params, sizeof(cb_params), cudaMemcpyHostToDevice)); + + std::string callback_name = "windowing_callback"; +#endif + size_t lto_callback_fatbin_size = sizeof(window_callback); + CHECK_ERROR(cufftXtSetJITCallback(inverse_plan_cb, + callback_name.c_str(), + (void*)window_callback, + lto_callback_fatbin_size, + CUFFT_CB_LD_COMPLEX, + (void **)&device_params)); + + CHECK_ERROR(cufftMakePlan1d(forward_plan, signal_size, CUFFT_R2C, batches, &work_size)); + CHECK_ERROR(cufftMakePlan1d(inverse_plan_cb, signal_size, CUFFT_C2R, batches, &work_size)); + + // Transform signal forward + printf("Transforming signal cufftExecR2C\n"); + CHECK_ERROR(cufftExecR2C(forward_plan, (cufftReal *)device_signals, (cufftComplex *)device_signals)); + + // Apply window via load callback and inverse-transform the signal + printf("Transforming signal cufftExecC2R\n"); + CHECK_ERROR(cufftExecC2R(inverse_plan_cb, (cufftComplex *)device_signals, (cufftReal *)device_signals)); + + // Copy device memory to host + CHECK_ERROR(cudaMemcpy(output_signals, device_signals, complex_size_bytes, cudaMemcpyDeviceToHost)); + + // Destroy CUFFT context + CHECK_ERROR(cufftDestroy(forward_plan)); + CHECK_ERROR(cufftDestroy(inverse_plan_cb)); + + // Cleanup memory + CHECK_ERROR(cudaFree(device_signals)); + CHECK_ERROR(cudaFree(device_params)); + + // Compute reference + if(reference_r2c_window_c2r(batches, signal_size, window_size, &input_signals[0][0], &reference[0][0]) != PASS_VALUE) { + printf("Failed to compute the reference"); + return ERROR_VALUE; + }; + + double l2_error = compute_error(&reference[0][0], &output_signals[0][0], batches, signal_size); + printf("L2 error: %e\n", l2_error); + + return (l2_error < threshold) ? PASS_VALUE : ERROR_VALUE; +} + +// Define global variables for size configuration +unsigned batches = 100; +unsigned signal_size = 128; +unsigned window_size = 16; +unsigned complex_signal_size = signal_size / 2 + 1; + +//////////////////////////////////////////////////////////////////////////////// +// Program main +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) { + // Parse size argument (small/medium/large) + const char* size_name = "small"; + if (argc > 1) { + if (strcmp(argv[1], "small") == 0) { + batches = 128; + signal_size = 64; + window_size = 16; + size_name = "small"; + } else if (strcmp(argv[1], "medium") == 0) { + batches = 500; + signal_size = 256; + window_size = 32; + size_name = "medium"; + } else if (strcmp(argv[1], "large") == 0) { + batches = 2000; + signal_size = 32; + window_size = 8; + size_name = "large"; + } + } + complex_signal_size = signal_size / 2 + 1; + + printf("==============================================\n"); + printf("cuFFT LTO R2C:C2R Example (Scalable)\n"); + printf("==============================================\n"); + printf("Size: %s\n", size_name); + printf("Signal size: %u\n", signal_size); + printf("Batches: %u\n", batches); + printf("Window size: %u\n", window_size); + printf("==============================================\n\n"); + + struct cudaDeviceProp properties; + int device; + CHECK_ERROR(cudaGetDevice(&device)); + CHECK_ERROR(cudaGetDeviceProperties(&properties, device)); + if (!(properties.major >= 5)) { + printf("cuFFT with LTO requires CUDA architecture SM5.0 or higher\n"); + return ERROR_VALUE; + } + + int result = test_r2c_window_c2r(); + + printf("\n==============================================\n"); + if (result == PASS_VALUE) { + printf("SUCCESS: LTO R2C:C2R completed\n"); + } else { + printf("FAILED: LTO R2C:C2R\n"); + } + printf("==============================================\n"); + + return result; +} \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h new file mode 100644 index 000000000..576c68378 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +/* A simplified version of the NVRTC helper included with CUDA samples, + * targeting cuFFT LTO callbacks + */ + +#ifndef COMMON_NVRTC_HELPER_H_ +#define COMMON_NVRTC_HELPER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define NVRTC_SAFE_CALL(Name, x) \ + do { \ + nvrtcResult result = x; \ + if (result != NVRTC_SUCCESS) { \ + std::cerr << "\nerror: " << Name << " failed with error " \ + << nvrtcGetErrorString(result) << std::endl; \ + exit(1); \ + } \ + } while (0) + +#define STRINGIZE2(s) #s +#define STRINGIZE(s) STRINGIZE2(s) +#define INCLUDE_CUDA_PATH "-I" STRINGIZE(CUDA_PATH) "/include" +#define CUDA_ARCH_FLAG "-arch=compute_" STRINGIZE(CUDA_ARCH) +#define CALLBACK_CODE_PATH(name) STRINGIZE(SOURCE_PATH) "/" name + +void compile_file_to_lto(std::vector& cubin_result, const char *filename) { + std::ifstream inputFile(filename, std::ios::in | std::ios::binary | std::ios::ate); + if (!inputFile.is_open()) { + std::cerr << "\nerror: unable to open " << filename << " for reading!\n"; + exit(1); + } + + std::streampos pos = inputFile.tellg(); + size_t inputSize = (size_t)pos; + std::vector memBlock(inputSize + 1); + + inputFile.seekg(0, std::ios::beg); + inputFile.read(memBlock.data(), inputSize); + inputFile.close(); + memBlock[inputSize] = '\x0'; + + const int num_params = 6; + const char *compile_params[] = {INCLUDE_CUDA_PATH, + CUDA_ARCH_FLAG, + "--std=c++11", + "--relocatable-device-code=true", + "-default-device", + "-dlto"}; + + // Compile + nvrtcProgram prog; + NVRTC_SAFE_CALL("nvrtcCreateProgram", nvrtcCreateProgram(&prog, memBlock.data(), filename, 0, NULL, NULL)); + nvrtcResult res = nvrtcCompileProgram(prog, num_params, compile_params); + + // Print log + size_t logSize; + NVRTC_SAFE_CALL("nvrtcGetProgramLogSize", nvrtcGetProgramLogSize(prog, &logSize)); + std::vector log(logSize + 1); + NVRTC_SAFE_CALL("nvrtcGetProgramLog", nvrtcGetProgramLog(prog, log.data())); + log[logSize] = '\x0'; + + if(log.size() > 2) { + std::cerr << "\n compilation log ---\n"; + std::string s(log.begin(), log.end()); + std::cerr << s; + std::cerr << "\n end log ---\n"; + } + + NVRTC_SAFE_CALL("nvrtcCompileProgram", res); + + size_t codeSize; + NVRTC_SAFE_CALL("nvrtcGetLTOIRSize", nvrtcGetLTOIRSize(prog, &codeSize)); + std::vector buffer(codeSize); + NVRTC_SAFE_CALL("nvrtcGetNVVM", nvrtcGetLTOIR(prog, buffer.data())); + cubin_result = buffer; +} + +#endif // COMMON_NVRTC_HELPER_H_ \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu new file mode 100644 index 000000000..7d838d028 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu @@ -0,0 +1,148 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + + +/* + * Example showing the use of LTO callbacks with CUFFT to perform + * normalization and truncation with zero padding. + * +*/ + +#include +#include +#include "common.h" +#include "r2c_c2r_reference.h" +#include "callback_params.h" + +// This is the store callback routine. It filters high frequencies +// based on a truncation window specified by the user +// NOTE: unlike the LTO version, the callback function can have +// any name +__constant__ unsigned cmem_window_size = window_size; +__constant__ unsigned cmem_signal_size = complex_signal_size; +__device__ cufftComplex windowing_callback(void *input, + size_t index, + void *info, + void *sharedmem) { + cufftComplex* cb_output = static_cast(input); +#ifdef CB_USE_CONSTANT_MEMORY + const unsigned sample = index % cmem_signal_size; + return (sample < cmem_window_size) ? cb_output[index] : cufftComplex{0.f, 0.f}; +#else + const cb_params* params = static_cast(info); + const unsigned sample = index % params->signal_size; + return (sample < params->window_size) ? cb_output[index] : cufftComplex{0.f, 0.f}; +#endif +} + +__device__ cufftCallbackLoadC device_callback_ptr = windowing_callback; + +static_assert(window_size < (signal_size/2 + 1), "The window size must be smaller than the signal size in complex space"); + +int test_r2c_window_c2r() { + + // Padded array for in-place transforms + float input_signals[batches][2 * complex_signal_size] = {}; + float output_signals[batches][2 * complex_signal_size]; + float reference[batches][2 * complex_signal_size]; + + init_input_signals(batches, signal_size, &input_signals[0][0]); + + const size_t complex_size_bytes = batches * complex_signal_size * 2 * sizeof(float); + + // Allocate and copy input from host to GPU + float *device_signals; + CHECK_ERROR(cudaMalloc((void **)&device_signals, complex_size_bytes)); + CHECK_ERROR(cudaMemcpy(device_signals, input_signals, complex_size_bytes, cudaMemcpyHostToDevice)); + + // Create a CUFFT plan for the forward transform, and a cuFFT plan for the inverse transform with load callback + cufftHandle forward_plan, inverse_plan_cb; + size_t work_size; + + CHECK_ERROR(cufftCreate(&forward_plan)); + CHECK_ERROR(cufftCreate(&inverse_plan_cb)); + + CHECK_ERROR(cufftMakePlan1d(forward_plan, signal_size, CUFFT_R2C, batches, &work_size)); + CHECK_ERROR(cufftMakePlan1d(inverse_plan_cb, signal_size, CUFFT_C2R, batches, &work_size)); + + // NOTE: The host needs to get a copy of the device pointer to the callback. Not required for LTO callback + cufftCallbackLoadC host_callback_ptr; + CHECK_ERROR(cudaMemcpyFromSymbol(&host_callback_ptr, device_callback_ptr, sizeof(host_callback_ptr))); + +#ifdef CB_USE_CONSTANT_MEMORY + cb_params *device_params = nullptr; +#else + // Define a structure used to pass in the window size + cb_params host_params; + host_params.window_size = window_size; + host_params.signal_size = complex_signal_size; + + // Allocate and copy callback parameters from host to GPU + cb_params *device_params; + CHECK_ERROR(cudaMalloc((void **)&device_params, sizeof(cb_params))); + CHECK_ERROR(cudaMemcpy(device_params, &host_params, sizeof(cb_params), cudaMemcpyHostToDevice)); +#endif + // Now associate the load callback with the plan. + CHECK_ERROR(cufftXtSetCallback(inverse_plan_cb, (void **)&host_callback_ptr, CUFFT_CB_LD_COMPLEX, (void **)&device_params)); + + // Transform signal forward + printf("Transforming signal cufftExecR2C\n"); + CHECK_ERROR(cufftExecR2C(forward_plan, (cufftReal *)device_signals, (cufftComplex *)device_signals)); + + // Apply window via load callback and inverse-transform the signal + printf("Transforming signal cufftExecC2R\n"); + CHECK_ERROR(cufftExecC2R(inverse_plan_cb, (cufftComplex *)device_signals, (cufftReal *)device_signals)); + + // Copy device memory to host + CHECK_ERROR(cudaMemcpy(output_signals, device_signals, complex_size_bytes, cudaMemcpyDeviceToHost)); + + // Destroy CUFFT context + CHECK_ERROR(cufftDestroy(forward_plan)); + CHECK_ERROR(cufftDestroy(inverse_plan_cb)); + + // Cleanup memory + CHECK_ERROR(cudaFree(device_signals)); + CHECK_ERROR(cudaFree(device_params)); + + // Compute reference + if (reference_r2c_window_c2r(batches, signal_size, window_size, &input_signals[0][0], &reference[0][0]) != PASS_VALUE) { + printf("Failed to compute the reference"); + return ERROR_VALUE; + } + + double l2_error = compute_error(&reference[0][0], &output_signals[0][0], batches, signal_size); + printf("L2 error: %e\n", l2_error); + + return (l2_error < threshold) ? PASS_VALUE : ERROR_VALUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Program main +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) { + struct cudaDeviceProp properties; + int device; + CHECK_ERROR(cudaGetDevice(&device)); + CHECK_ERROR(cudaGetDeviceProperties(&properties, device)); + if (!(properties.major >= 5)) { + printf("cuFFT with LTO requires CUDA architecture SM5.0 or higher\n"); + return ERROR_VALUE; + } + + return test_r2c_window_c2r(); +} diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu new file mode 100644 index 000000000..1431e3c1b --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + + +/* + * Example showing the use of LTO callbacks with CUFFT to perform + * truncation with zero padding. + * +*/ + +#include +#include "callback_params.h" + +// This is the store callback routine. It filters high frequencies +// based on a truncation window specified by the user +__device__ cufftComplex windowing_callback(void* input, + unsigned long long idx, + void* info, + void* sharedmem) { + + const cb_params* params = static_cast(info); + cufftComplex* cb_output = static_cast(input); + + const unsigned sample = idx % params->signal_size; + + return (sample < params->window_size) ? cb_output[idx] : cufftComplex{0.f, 0.f}; +} + +// Note: Constant memory version removed for scalable implementation +// The callback now uses runtime parameters passed through info pointer \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_nvrtc_callback_example.cpp b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_nvrtc_callback_example.cpp new file mode 100644 index 000000000..4ec57e6bb --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_nvrtc_callback_example.cpp @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + + +/* + * Example showing the use of LTO callbacks with CUFFT to perform + * truncation with zero padding. + * +*/ + +#include +#include +#include "r2c_c2r_reference.h" +#include "common.h" +#include "nvrtc_helper.h" +#include "callback_params.h" +static_assert(window_size < (signal_size/2 + 1), "The window size must be smaller than the signal size in complex space"); + +int test_r2c_window_c2r() { + // Padded array for in-place transforms + float input_signals[batches][2 * complex_signal_size] = {}; + float output_signals[batches][2 * complex_signal_size]; + float reference[batches][2 * complex_signal_size]; + + init_input_signals(batches, signal_size, &input_signals[0][0]); + + const size_t complex_size_bytes = batches * complex_signal_size * 2 * sizeof(float); + + // Allocate and copy input from host to GPU + float *device_signals; + CHECK_ERROR(cudaMalloc((void **)&device_signals, complex_size_bytes)); + CHECK_ERROR(cudaMemcpy(device_signals, input_signals, complex_size_bytes, cudaMemcpyHostToDevice)); + + // NOTE: Use NVRTC to compile the callback function to LTO + std::vector callback_buffer; + compile_file_to_lto(callback_buffer, CALLBACK_CODE_PATH("r2c_c2r_lto_callback_device.cu")); + + // Create a CUFFT plan for the forward transform, and a cuFFT plan for the inverse transform with load callback + cufftHandle forward_plan, inverse_plan_cb; + size_t work_size; + + CHECK_ERROR(cufftCreate(&forward_plan)); + CHECK_ERROR(cufftCreate(&inverse_plan_cb)); + + // NOTE: LTO callbacks must be set before plan creation and cannot be unset (yet) +#ifdef CB_USE_CONSTANT_MEMORY + cb_params *device_params = nullptr; + std::string callback_name = "windowing_constant_memory_callback"; +#else + // Define a structure used to pass in the window size + cb_params host_params; + host_params.window_size = window_size; + host_params.signal_size = complex_signal_size; + + // Allocate and copy callback parameters from host to GPU + cb_params *device_params; + CHECK_ERROR(cudaMalloc((void **)&device_params, sizeof(cb_params))); + CHECK_ERROR(cudaMemcpy(device_params, &host_params, sizeof(cb_params), cudaMemcpyHostToDevice)); + + std::string callback_name = "windowing_callback"; +#endif + CHECK_ERROR(cufftXtSetJITCallback(inverse_plan_cb, + callback_name.c_str(), + (void*)callback_buffer.data(), + callback_buffer.size(), + CUFFT_CB_LD_COMPLEX, + (void **)&device_params)); + + CHECK_ERROR(cufftMakePlan1d(forward_plan, signal_size, CUFFT_R2C, batches, &work_size)); + CHECK_ERROR(cufftMakePlan1d(inverse_plan_cb, signal_size, CUFFT_C2R, batches, &work_size)); + + // Transform signal forward + printf("Transforming signal cufftExecR2C\n"); + CHECK_ERROR(cufftExecR2C(forward_plan, (cufftReal *)device_signals, (cufftComplex *)device_signals)); + + // Apply window via load callback and inverse-transform the signal + printf("Transforming signal cufftExecC2R\n"); + CHECK_ERROR(cufftExecC2R(inverse_plan_cb, (cufftComplex *)device_signals, (cufftReal *)device_signals)); + + // Copy device memory to host + CHECK_ERROR(cudaMemcpy(output_signals, device_signals, complex_size_bytes, cudaMemcpyDeviceToHost)); + + // Destroy CUFFT context + CHECK_ERROR(cufftDestroy(forward_plan)); + CHECK_ERROR(cufftDestroy(inverse_plan_cb)); + + // Cleanup memory + CHECK_ERROR(cudaFree(device_signals)); + CHECK_ERROR(cudaFree(device_params)); + + // Compute reference + if (reference_r2c_window_c2r(batches, signal_size, window_size, input_signals[0], reference[0]) != PASS_VALUE) { + printf("Failed to compute the reference"); + return ERROR_VALUE; + } + + double l2_error = compute_error(reference[0], output_signals[0], batches, signal_size); + printf("L2 error: %e\n", l2_error); + + return (l2_error < threshold) ? PASS_VALUE : ERROR_VALUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Program main +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) { + struct cudaDeviceProp properties; + int device; + CHECK_ERROR(cudaGetDevice(&device)); + CHECK_ERROR(cudaGetDeviceProperties(&properties, device)); + if (!(properties.major >= 5)) { + printf("cuFFT with LTO requires CUDA architecture SM5.0 or higher\n"); + return ERROR_VALUE; + } + + return test_r2c_window_c2r(); +} diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.cu b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.cu new file mode 100644 index 000000000..b040bfd54 --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.cu @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + +/* + * Reference for the example of LTO callbacks, + * run the same plans but perform the windowing with + * a separate kernel. + * +*/ + +#include +#include +#include +#include +#include "r2c_c2r_reference.h" + +__global__ void windowing(unsigned nbatches, unsigned complex_signal_size, unsigned window_size, float2* buffer) +{ + const size_t idx = blockIdx.x*blockDim.x + threadIdx.x; + + if(idx >= nbatches * complex_signal_size) return; + + const unsigned sample = idx % complex_signal_size; + + buffer[idx].x = (sample < window_size) ? buffer[idx].x : 0.f; + buffer[idx].y = (sample < window_size) ? buffer[idx].y : 0.f; +} + +int reference_r2c_window_c2r(unsigned batches, unsigned signal_size, unsigned window_size, float* input_signals, float* output_signals) { + const unsigned complex_signal_size = signal_size / 2 + 1; + const size_t complex_size_bytes = batches * complex_signal_size * 2 * sizeof(float); + + // Allocate and copy input from host to GPU + float *device_signals; + CHECK_ERROR(cudaMalloc((void **)&device_signals, complex_size_bytes)); + CHECK_ERROR(cudaMemcpy(device_signals, input_signals, complex_size_bytes, cudaMemcpyHostToDevice)); + + // Create a CUFFT plan for the forward transform, and a cuFFT plan for the inverse transform + cufftHandle forward_plan, inverse_plan; + size_t work_size; + + CHECK_ERROR(cufftCreate(&forward_plan)); + CHECK_ERROR(cufftCreate(&inverse_plan)); + + CHECK_ERROR(cufftMakePlan1d(forward_plan, signal_size, CUFFT_R2C, batches, &work_size)); + CHECK_ERROR(cufftMakePlan1d(inverse_plan, signal_size, CUFFT_C2R, batches, &work_size)); + + // Transform signal forward + printf("Transforming reference cufftExecR2C\n"); + CHECK_ERROR(cufftExecR2C(forward_plan, (cufftReal *)device_signals, (cufftComplex *)device_signals)); + + // Apply window via separate kernel + windowing<<<(batches * complex_signal_size + 255) / 256, 256>>>(batches, complex_signal_size, window_size, (float2*) device_signals); + cudaDeviceSynchronize(); + CHECK_ERROR(cudaGetLastError()); + + // Inverse-transform the signal + printf("Transforming reference cufftExecC2R\n"); + CHECK_ERROR(cufftExecC2R(inverse_plan, (cufftComplex *)device_signals, (cufftReal *)device_signals)); + + // Copy device memory to host + CHECK_ERROR(cudaMemcpy(output_signals, device_signals, complex_size_bytes, cudaMemcpyDeviceToHost)); + + // Destroy CUFFT context + CHECK_ERROR(cufftDestroy(forward_plan)); + CHECK_ERROR(cufftDestroy(inverse_plan)); + + // Cleanup memory + CHECK_ERROR(cudaFree(device_signals)); + + return PASS_VALUE; +} \ No newline at end of file diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.h b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.h new file mode 100644 index 000000000..9872fd98d --- /dev/null +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.h @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#ifndef R2C_C2R_REFERENCE__H_ +#define R2C_C2R_REFERENCE__H_ + +#include "common.h" + +int reference_r2c_window_c2r(unsigned batches, unsigned signal_size, unsigned window_size, float* input_signals, float* output_signals); + +#endif // R2C_C2R_REFERENCE__H_ \ No newline at end of file diff --git a/src/cuda/H100/cusolver/cusolver_Xgetrf/Makefile b/src/cuda/H100/cusolver/cusolver_Xgetrf/Makefile new file mode 100644 index 000000000..8fdea2698 --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_Xgetrf/Makefile @@ -0,0 +1,9 @@ +EXECUTABLE := cusolver_Xgetrf_scalable +CUFILES := cusolver_Xgetrf_scalable.cu +CCFILES := +ADDITIONAL_LIBS := -lcusolver -lcublas +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +include ../../../common/common.mk diff --git a/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu b/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu new file mode 100644 index 000000000..28667d97d --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu @@ -0,0 +1,268 @@ +/* + * Modified cuSOLVER Xgetrf example with scalable input sizes + * Based on NVIDIA's cusolver_Xgetrf_example.cu + * + * Accepts command-line arguments for matrix size: + * small: 16x16 + * medium: 128x128 + * large: 512x512 + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "cusolver_utils.h" + +void print_usage(const char* prog_name) { + printf("Usage: %s [size]\n", prog_name); + printf("\n"); + printf("Arguments:\n"); + printf(" size Matrix size: small (16x16), medium (128x128), large (512x512)\n"); + printf(" Default: small\n"); + printf("\n"); + printf("Examples:\n"); + printf(" %s small # 16x16 matrix\n", prog_name); + printf(" %s medium # 128x128 matrix\n", prog_name); + printf(" %s large # 512x512 matrix\n", prog_name); + printf("\n"); +} + +int main(int argc, char *argv[]) { + // Parse command line arguments + int64_t m = 16; // Default: small + const char* size_name = "small"; + + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + print_usage(argv[0]); + return 0; + } + + if (strcmp(argv[1], "small") == 0) { + m = 16; + size_name = "small"; + } else if (strcmp(argv[1], "medium") == 0) { + m = 128; + size_name = "medium"; + } else if (strcmp(argv[1], "large") == 0) { + m = 512; + size_name = "large"; + } else { + fprintf(stderr, "Error: Unknown size '%s'\n", argv[1]); + fprintf(stderr, "Valid sizes: small, medium, large\n"); + return 1; + } + } + + printf("==============================================\n"); + printf("cuSOLVER Xgetrf Example (Scalable)\n"); + printf("==============================================\n"); + printf("Matrix size: %s (%ldx%ld)\n", size_name, m, m); + printf("Pivot: ON (compute P*A = L*U)\n"); + printf("==============================================\n\n"); + + cusolverDnHandle_t cusolverH = NULL; + cudaStream_t stream = NULL; + + using data_type = double; + + const int64_t lda = m; + const int64_t ldb = m; + const int64_t nrhs = 1; // number of right-hand sides + + // Generate random matrix A and vector B + std::vector A(m * m); + std::vector B(m); + std::vector X(m, 0); + std::vector LU(lda * m, 0); + std::vector Ipiv(m, 0); + int info = 0; + + // Initialize with random values for reproducibility + std::mt19937 gen(42); // Fixed seed for reproducibility + std::uniform_real_distribution dist(0.0, 10.0); + + for (int64_t i = 0; i < m * m; i++) { + A[i] = dist(gen); + } + + for (int64_t i = 0; i < m; i++) { + B[i] = dist(gen); + } + + // For small matrices, print them + if (m <= 16) { + std::printf("A = (matlab base-1)\n"); + for (int64_t row = 0; row < m; row++) { + for (int64_t col = 0; col < m; col++) { + printf("%.2f ", A[col * m + row]); + } + printf("\n"); + } + std::printf("=====\n"); + std::printf("B = (matlab base-1)\n"); + for (int64_t i = 0; i < m; i++) { + printf("%.2f ", B[i]); + } + printf("\n"); + std::printf("=====\n"); + } + + data_type *d_A = nullptr; /* device copy of A */ + data_type *d_B = nullptr; /* device copy of B */ + int64_t *d_Ipiv = nullptr; /* pivoting sequence */ + int *d_info = nullptr; /* error info */ + + size_t workspaceInBytesOnDevice = 0; /* size of workspace */ + void *d_work = nullptr; /* device workspace for getrf */ + size_t workspaceInBytesOnHost = 0; /* size of workspace */ + void *h_work = nullptr; /* host workspace for getrf */ + + const int pivot_on = 1; + const int algo = 0; + + printf("Using New Algo\n"); + + /* step 1: create cusolver handle, bind a stream */ + CUSOLVER_CHECK(cusolverDnCreate(&cusolverH)); + + CUDA_CHECK(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking)); + CUSOLVER_CHECK(cusolverDnSetStream(cusolverH, stream)); + + /* step 2: copy A to device */ + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_A), sizeof(data_type) * A.size())); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_B), sizeof(data_type) * B.size())); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_Ipiv), sizeof(int64_t) * Ipiv.size())); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_info), sizeof(int))); + + CUDA_CHECK(cudaMemcpyAsync(d_A, A.data(), sizeof(data_type) * A.size(), + cudaMemcpyHostToDevice, stream)); + CUDA_CHECK(cudaMemcpyAsync(d_B, B.data(), sizeof(data_type) * B.size(), + cudaMemcpyHostToDevice, stream)); + + /* step 3: query working space of Xgetrf */ + cusolverDnParams_t params; + CUSOLVER_CHECK(cusolverDnCreateParams(¶ms)); + + CUSOLVER_CHECK(cusolverDnXgetrf_bufferSize(cusolverH, params, m, m, + CUDA_R_64F, d_A, lda, + CUDA_R_64F, &workspaceInBytesOnDevice, + &workspaceInBytesOnHost)); + + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_work), workspaceInBytesOnDevice)); + + if (workspaceInBytesOnHost > 0) { + h_work = malloc(workspaceInBytesOnHost); + if (h_work == nullptr) { + throw std::runtime_error("Error: h_work not allocated."); + } + } + + printf("Starting LU factorization (Xgetrf)...\n"); + printf("Workspace: device=%zu bytes, host=%zu bytes\n", + workspaceInBytesOnDevice, workspaceInBytesOnHost); + + /* step 4: LU factorization */ + CUSOLVER_CHECK(cusolverDnXgetrf(cusolverH, params, m, m, + CUDA_R_64F, d_A, lda, d_Ipiv, + CUDA_R_64F, d_work, workspaceInBytesOnDevice, + h_work, workspaceInBytesOnHost, d_info)); + + CUDA_CHECK(cudaMemcpyAsync(&info, d_info, sizeof(int), cudaMemcpyDeviceToHost, stream)); + CUDA_CHECK(cudaMemcpyAsync(LU.data(), d_A, sizeof(data_type) * A.size(), + cudaMemcpyDeviceToHost, stream)); + CUDA_CHECK(cudaMemcpyAsync(Ipiv.data(), d_Ipiv, sizeof(int64_t) * Ipiv.size(), + cudaMemcpyDeviceToHost, stream)); + + CUDA_CHECK(cudaStreamSynchronize(stream)); + + std::printf("after Xgetrf: info = %d\n", info); + if (0 > info) { + std::printf("%d-th parameter is wrong \n", -info); + exit(1); + } else if (0 < info) { + std::printf("WARNING: matrix is singular, U(%d,%d) = 0\n", info, info); + } + + // For small matrices, print pivoting sequence + if (m <= 16) { + printf("pivoting sequence, matlab base-1\n"); + for (int64_t i = 0; i < m; i++) { + printf("Ipiv(%ld) = %ld\n", i + 1, Ipiv[i]); + } + } else { + printf("pivoting sequence (first 8), matlab base-1\n"); + for (int i = 0; i < std::min((int64_t)8, m); i++) { + printf("Ipiv(%d) = %ld\n", i + 1, Ipiv[i]); + } + } + + // For small matrices, print L and U + if (m <= 16) { + printf("L and U = (matlab base-1)\n"); + for (int64_t row = 0; row < m; row++) { + for (int64_t col = 0; col < m; col++) { + printf("%.2f ", LU[col * m + row]); + } + printf("\n"); + } + std::printf("=====\n"); + } + + printf("Starting solve (Xgetrs)...\n"); + + /* step 5: solve A*X = B */ + CUSOLVER_CHECK(cusolverDnXgetrs(cusolverH, params, CUBLAS_OP_N, m, nrhs, + CUDA_R_64F, d_A, lda, d_Ipiv, + CUDA_R_64F, d_B, ldb, d_info)); + + CUDA_CHECK(cudaMemcpyAsync(X.data(), d_B, sizeof(data_type) * X.size(), + cudaMemcpyDeviceToHost, stream)); + + CUDA_CHECK(cudaStreamSynchronize(stream)); + + // Print solution + if (m <= 16) { + std::printf("X = (matlab base-1)\n"); + for (int64_t i = 0; i < m; i++) { + printf("%.6f ", X[i]); + } + printf("\n"); + } else { + std::printf("X = (first 8 elements)\n"); + for (int i = 0; i < std::min((int64_t)8, m); i++) { + printf("%.6f ", X[i]); + } + printf("\n"); + } + + printf("\n==============================================\n"); + printf("SUCCESS: LU factorization and solve completed\n"); + printf("==============================================\n"); + + /* free resources */ + CUDA_CHECK(cudaFree(d_A)); + CUDA_CHECK(cudaFree(d_B)); + CUDA_CHECK(cudaFree(d_Ipiv)); + CUDA_CHECK(cudaFree(d_info)); + CUDA_CHECK(cudaFree(d_work)); + + CUSOLVER_CHECK(cusolverDnDestroyParams(params)); + CUSOLVER_CHECK(cusolverDnDestroy(cusolverH)); + + CUDA_CHECK(cudaStreamDestroy(stream)); + + if (h_work) { + free(h_work); + } + + CUDA_CHECK(cudaDeviceReset()); + + return EXIT_SUCCESS; +} diff --git a/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_utils.h b/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_utils.h new file mode 100644 index 000000000..2d37d51c5 --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_Xgetrf/cusolver_utils.h @@ -0,0 +1,299 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2019 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// CUDA API error checking +#define CUDA_CHECK(err) \ + do { \ + cudaError_t err_ = (err); \ + if (err_ != cudaSuccess) { \ + printf("CUDA error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("CUDA error"); \ + } \ + } while (0) + +// cusolver API error checking +#define CUSOLVER_CHECK(err) \ + do { \ + cusolverStatus_t err_ = (err); \ + if (err_ != CUSOLVER_STATUS_SUCCESS) { \ + printf("cusolver error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cusolver error"); \ + } \ + } while (0) + +// cublas API error checking +#define CUBLAS_CHECK(err) \ + do { \ + cublasStatus_t err_ = (err); \ + if (err_ != CUBLAS_STATUS_SUCCESS) { \ + printf("cublas error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cublas error"); \ + } \ + } while (0) + +// cublas API error checking +#define CUSPARSE_CHECK(err) \ + do { \ + cusparseStatus_t err_ = (err); \ + if (err_ != CUSPARSE_STATUS_SUCCESS) { \ + printf("cusparse error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cusparse error"); \ + } \ + } while (0) + +// memory alignment +#define ALIGN_TO(A, B) (((A + B - 1) / B) * B) + +// device memory pitch alignment +static const size_t device_alignment = 32; + +// type traits +template struct traits; + +template <> struct traits { + // scalar type + typedef float T; + typedef T S; + + static constexpr T zero = 0.f; + static constexpr cudaDataType cuda_data_type = CUDA_R_32F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_R_32F; +#endif + + inline static S abs(T val) { return fabs(val); } + + template inline static T rand(RNG &gen) { return (S)gen(); } + + inline static T add(T a, T b) { return a + b; } + + inline static T mul(T v, S f) { return v * f; } +}; + +template <> struct traits { + // scalar type + typedef double T; + typedef T S; + + static constexpr T zero = 0.; + static constexpr cudaDataType cuda_data_type = CUDA_R_64F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_R_64F; +#endif + + inline static S abs(T val) { return fabs(val); } + + template inline static T rand(RNG &gen) { return (S)gen(); } + + inline static T add(T a, T b) { return a + b; } + + inline static T mul(T v, S f) { return v * f; } +}; + +template <> struct traits { + // scalar type + typedef float S; + typedef cuFloatComplex T; + + static constexpr T zero = {0.f, 0.f}; + static constexpr cudaDataType cuda_data_type = CUDA_C_32F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_C_32F; +#endif + + inline static S abs(T val) { return cuCabsf(val); } + + template inline static T rand(RNG &gen) { + return make_cuFloatComplex((S)gen(), (S)gen()); + } + + inline static T add(T a, T b) { return cuCaddf(a, b); } + inline static T add(T a, S b) { return cuCaddf(a, make_cuFloatComplex(b, 0.f)); } + + inline static T mul(T v, S f) { return make_cuFloatComplex(v.x * f, v.y * f); } +}; + +template <> struct traits { + // scalar type + typedef double S; + typedef cuDoubleComplex T; + + static constexpr T zero = {0., 0.}; + static constexpr cudaDataType cuda_data_type = CUDA_C_64F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_C_64F; +#endif + + inline static S abs(T val) { return cuCabs(val); } + + template inline static T rand(RNG &gen) { + return make_cuDoubleComplex((S)gen(), (S)gen()); + } + + inline static T add(T a, T b) { return cuCadd(a, b); } + inline static T add(T a, S b) { return cuCadd(a, make_cuDoubleComplex(b, 0.)); } + + inline static T mul(T v, S f) { return make_cuDoubleComplex(v.x * f, v.y * f); } +}; + +template void print_matrix(const int &m, const int &n, const T *A, const int &lda); + +template <> void print_matrix(const int &m, const int &n, const float *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f ", A[j * lda + i]); + } + std::printf("\n"); + } +} + +template <> void print_matrix(const int &m, const int &n, const double *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f ", A[j * lda + i]); + } + std::printf("\n"); + } +} + +template <> void print_matrix(const int &m, const int &n, const cuComplex *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f + %0.2fj ", A[j * lda + i].x, A[j * lda + i].y); + } + std::printf("\n"); + } +} + +template <> +void print_matrix(const int &m, const int &n, const cuDoubleComplex *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f + %0.2fj ", A[j * lda + i].x, A[j * lda + i].y); + } + std::printf("\n"); + } +} + +template +void generate_random_matrix(cusolver_int_t m, cusolver_int_t n, T **A, int *lda) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution::S> dis(-1.0, 1.0); + auto rand_gen = std::bind(dis, gen); + + *lda = n; + + size_t matrix_mem_size = static_cast(*lda * m * sizeof(T)); + // suppress gcc 7 size warning + if (matrix_mem_size <= PTRDIFF_MAX) + *A = (T *)malloc(matrix_mem_size); + else + throw std::runtime_error("Memory allocation size is too large"); + + if (*A == NULL) + throw std::runtime_error("Unable to allocate host matrix"); + + for (int j = 0; j < n; ++j) { + for (int i = 0; i < m; ++i) { + T *A_col = (*A) + *lda * j; + A_col[i] = traits::rand(rand_gen); + } + } +} + +// Makes matrix A of size mxn and leading dimension lda diagonal dominant +template +void make_diag_dominant_matrix(cusolver_int_t m, cusolver_int_t n, T *A, int lda) { + for (int j = 0; j < std::min(m, n); ++j) { + T *A_col = A + lda * j; + auto col_sum = traits::S>::zero; + for (int i = 0; i < m; ++i) { + col_sum += traits::abs(A_col[i]); + } + A_col[j] = traits::add(A_col[j], col_sum); + } +} + +// Returns cudaDataType value as defined in library_types.h for the string containing type name +cudaDataType get_cuda_library_type(std::string type_string) { + if (type_string.compare("CUDA_R_16F") == 0) + return CUDA_R_16F; + else if (type_string.compare("CUDA_C_16F") == 0) + return CUDA_C_16F; + else if (type_string.compare("CUDA_R_32F") == 0) + return CUDA_R_32F; + else if (type_string.compare("CUDA_C_32F") == 0) + return CUDA_C_32F; + else if (type_string.compare("CUDA_R_64F") == 0) + return CUDA_R_64F; + else if (type_string.compare("CUDA_C_64F") == 0) + return CUDA_C_64F; + else if (type_string.compare("CUDA_R_8I") == 0) + return CUDA_R_8I; + else if (type_string.compare("CUDA_C_8I") == 0) + return CUDA_C_8I; + else if (type_string.compare("CUDA_R_8U") == 0) + return CUDA_R_8U; + else if (type_string.compare("CUDA_C_8U") == 0) + return CUDA_C_8U; + else if (type_string.compare("CUDA_R_32I") == 0) + return CUDA_R_32I; + else if (type_string.compare("CUDA_C_32I") == 0) + return CUDA_C_32I; + else if (type_string.compare("CUDA_R_32U") == 0) + return CUDA_R_32U; + else if (type_string.compare("CUDA_C_32U") == 0) + return CUDA_C_32U; + else + throw std::runtime_error("Unknown CUDA datatype"); +} + +// Returns cusolverIRSRefinement_t value as defined in cusolver_common.h for the string containing +// solver name +cusolverIRSRefinement_t get_cusolver_refinement_solver(std::string solver_string) { + if (solver_string.compare("CUSOLVER_IRS_REFINE_NONE") == 0) + return CUSOLVER_IRS_REFINE_NONE; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_CLASSICAL") == 0) + return CUSOLVER_IRS_REFINE_CLASSICAL; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_GMRES") == 0) + return CUSOLVER_IRS_REFINE_GMRES; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_CLASSICAL_GMRES") == 0) + return CUSOLVER_IRS_REFINE_CLASSICAL_GMRES; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_GMRES_GMRES") == 0) + return CUSOLVER_IRS_REFINE_GMRES_GMRES; + else + printf("Unknown solver parameter: \"%s\"\n", solver_string.c_str()); + + return CUSOLVER_IRS_REFINE_NOT_SET; +} \ No newline at end of file diff --git a/src/cuda/H100/cusolver/cusolver_ormqr/Makefile b/src/cuda/H100/cusolver/cusolver_ormqr/Makefile new file mode 100644 index 000000000..b7430b74e --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_ormqr/Makefile @@ -0,0 +1,9 @@ +EXECUTABLE := cusolver_ormqr_scalable +CUFILES := cusolver_ormqr_scalable.cu +CCFILES := +ADDITIONAL_LIBS := -lcusolver -lcublas +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +include ../../../common/common.mk diff --git a/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu new file mode 100644 index 000000000..ee0141fc0 --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu @@ -0,0 +1,235 @@ +/* + * Modified cuSOLVER ormqr example with scalable input sizes + * Based on NVIDIA's cusolver_ormqr_example.cu + * + * Accepts command-line arguments for matrix size: + * small: 16x16 + * medium: 128x128 + * large: 512x512 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cusolver_utils.h" + +void print_usage(const char* prog_name) { + printf("Usage: %s [size]\n", prog_name); + printf("\n"); + printf("Arguments:\n"); + printf(" size Matrix size: small (16x16), medium (128x128), large (512x512)\n"); + printf(" Default: small\n"); + printf("\n"); + printf("Examples:\n"); + printf(" %s small # 16x16 matrix\n", prog_name); + printf(" %s medium # 128x128 matrix\n", prog_name); + printf(" %s large # 512x512 matrix\n", prog_name); + printf("\n"); +} + +int main(int argc, char *argv[]) { + // Parse command line arguments + int m = 16; // Default: small + const char* size_name = "small"; + + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + print_usage(argv[0]); + return 0; + } + + if (strcmp(argv[1], "small") == 0) { + m = 16; + size_name = "small"; + } else if (strcmp(argv[1], "medium") == 0) { + m = 256; + size_name = "medium"; + } else if (strcmp(argv[1], "large") == 0) { + m = 768; + size_name = "large"; + } else { + fprintf(stderr, "Error: Unknown size '%s'\n", argv[1]); + fprintf(stderr, "Valid sizes: small, medium, large\n"); + return 1; + } + } + + printf("==============================================\n"); + printf("cuSOLVER ormqr Example (Scalable)\n"); + printf("==============================================\n"); + printf("Matrix size: %s (%dx%d)\n", size_name, m, m); + printf("==============================================\n\n"); + + cusolverDnHandle_t cusolverH = NULL; + cublasHandle_t cublasH = NULL; + cudaStream_t stream{}; + + const int lda = m; + const int ldb = m; + const int nrhs = 1; // number of right hand side vectors + + // Generate random matrix A and vector B + std::vector A(m * m); + std::vector B(m); + std::vector XC(ldb * nrhs, 0); // solution matrix from GPU + + // Initialize with random values for reproducibility + std::mt19937 gen(42); // Fixed seed for reproducibility + std::uniform_real_distribution dist(0.0, 10.0); + + for (int i = 0; i < m * m; i++) { + A[i] = dist(gen); + } + + for (int i = 0; i < m; i++) { + B[i] = dist(gen); + } + + // For small matrices, print them + if (m <= 16) { + std::printf("A = (first 8x8 block, matlab base-1)\n"); + int print_size = std::min(m, 8); + for (int row = 0; row < print_size; row++) { + for (int col = 0; col < print_size; col++) { + printf("%.2f ", A[col * m + row]); + } + printf("\n"); + } + std::printf("=====\n"); + std::printf("B = (first 8 elements, matlab base-1)\n"); + for (int i = 0; i < std::min(m, 8); i++) { + printf("%.2f ", B[i]); + } + printf("\n"); + std::printf("=====\n"); + } + + /* device memory */ + double *d_A = nullptr; + double *d_tau = nullptr; + double *d_B = nullptr; + int *d_info = nullptr; + double *d_work = nullptr; + + int lwork_geqrf = 0; + int lwork_ormqr = 0; + int lwork = 0; + int info = 0; + + const double one = 1; + + /* step 1: create cudense/cublas handle */ + CUSOLVER_CHECK(cusolverDnCreate(&cusolverH)); + CUBLAS_CHECK(cublasCreate(&cublasH)); + + CUDA_CHECK(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking)); + CUSOLVER_CHECK(cusolverDnSetStream(cusolverH, stream)); + CUBLAS_CHECK(cublasSetStream(cublasH, stream)); + + /* step 2: copy A and B to device */ + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_A), sizeof(double) * A.size())); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_tau), sizeof(double) * m)); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_B), sizeof(double) * B.size())); + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_info), sizeof(int))); + + CUDA_CHECK( + cudaMemcpyAsync(d_A, A.data(), sizeof(double) * A.size(), cudaMemcpyHostToDevice, stream)); + CUDA_CHECK( + cudaMemcpyAsync(d_B, B.data(), sizeof(double) * B.size(), cudaMemcpyHostToDevice, stream)); + + /* step 3: query working space of geqrf and ormqr */ + CUSOLVER_CHECK(cusolverDnDgeqrf_bufferSize(cusolverH, m, m, d_A, lda, &lwork_geqrf)); + + CUSOLVER_CHECK(cusolverDnDormqr_bufferSize(cusolverH, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, m, nrhs, m, + d_A, lda, d_tau, d_B, ldb, &lwork_ormqr)); + + lwork = std::max(lwork_geqrf, lwork_ormqr); + + CUDA_CHECK(cudaMalloc(reinterpret_cast(&d_work), sizeof(double) * lwork)); + + printf("Starting QR factorization (geqrf)...\n"); + + /* step 4: compute QR factorization */ + CUSOLVER_CHECK(cusolverDnDgeqrf(cusolverH, m, m, d_A, lda, d_tau, d_work, lwork, d_info)); + + /* check if QR is good or not */ + CUDA_CHECK(cudaMemcpyAsync(&info, d_info, sizeof(int), cudaMemcpyDeviceToHost, stream)); + + CUDA_CHECK(cudaStreamSynchronize(stream)); + + std::printf("after geqrf: info = %d\n", info); + if (0 > info) { + std::printf("%d-th parameter is wrong \n", -info); + exit(1); + } + + printf("Starting ormqr (Q^T * B)...\n"); + + /* step 5: compute Q^T*B */ + CUSOLVER_CHECK(cusolverDnDormqr(cusolverH, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, m, nrhs, m, d_A, lda, + d_tau, d_B, ldb, d_work, lwork, d_info)); + + /* check if QR is good or not */ + CUDA_CHECK(cudaMemcpyAsync(&info, d_info, sizeof(int), cudaMemcpyDeviceToHost, stream)); + + CUDA_CHECK(cudaStreamSynchronize(stream)); + + std::printf("after ormqr: info = %d\n", info); + if (0 > info) { + std::printf("%d-th parameter is wrong \n", -info); + exit(1); + } + + printf("Computing x = R \\ Q^T*B (triangular solve)...\n"); + + /* step 6: compute x = R \ Q^T*B */ + CUBLAS_CHECK(cublasDtrsm(cublasH, CUBLAS_SIDE_LEFT, CUBLAS_FILL_MODE_UPPER, CUBLAS_OP_N, + CUBLAS_DIAG_NON_UNIT, m, nrhs, &one, d_A, lda, d_B, ldb)); + + CUDA_CHECK(cudaMemcpyAsync(XC.data(), d_B, sizeof(double) * XC.size(), cudaMemcpyDeviceToHost, + stream)); + + CUDA_CHECK(cudaStreamSynchronize(stream)); + + // For small matrices, print solution + if (m <= 16) { + std::printf("X = (matlab base-1)\n"); + for (int i = 0; i < m; i++) { + printf("%.6f ", XC[i]); + } + printf("\n"); + } else { + std::printf("X = (first 8 elements)\n"); + for (int i = 0; i < std::min(m, 8); i++) { + printf("%.6f ", XC[i]); + } + printf("\n"); + } + + printf("\n==============================================\n"); + printf("SUCCESS: QR factorization and solve completed\n"); + printf("==============================================\n"); + + /* free resources */ + CUDA_CHECK(cudaFree(d_A)); + CUDA_CHECK(cudaFree(d_tau)); + CUDA_CHECK(cudaFree(d_B)); + CUDA_CHECK(cudaFree(d_info)); + CUDA_CHECK(cudaFree(d_work)); + + CUBLAS_CHECK(cublasDestroy(cublasH)); + CUSOLVER_CHECK(cusolverDnDestroy(cusolverH)); + + CUDA_CHECK(cudaStreamDestroy(stream)); + + CUDA_CHECK(cudaDeviceReset()); + + return EXIT_SUCCESS; +} diff --git a/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_utils.h b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_utils.h new file mode 100644 index 000000000..2d37d51c5 --- /dev/null +++ b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_utils.h @@ -0,0 +1,299 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2019 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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. + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// CUDA API error checking +#define CUDA_CHECK(err) \ + do { \ + cudaError_t err_ = (err); \ + if (err_ != cudaSuccess) { \ + printf("CUDA error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("CUDA error"); \ + } \ + } while (0) + +// cusolver API error checking +#define CUSOLVER_CHECK(err) \ + do { \ + cusolverStatus_t err_ = (err); \ + if (err_ != CUSOLVER_STATUS_SUCCESS) { \ + printf("cusolver error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cusolver error"); \ + } \ + } while (0) + +// cublas API error checking +#define CUBLAS_CHECK(err) \ + do { \ + cublasStatus_t err_ = (err); \ + if (err_ != CUBLAS_STATUS_SUCCESS) { \ + printf("cublas error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cublas error"); \ + } \ + } while (0) + +// cublas API error checking +#define CUSPARSE_CHECK(err) \ + do { \ + cusparseStatus_t err_ = (err); \ + if (err_ != CUSPARSE_STATUS_SUCCESS) { \ + printf("cusparse error %d at %s:%d\n", err_, __FILE__, __LINE__); \ + throw std::runtime_error("cusparse error"); \ + } \ + } while (0) + +// memory alignment +#define ALIGN_TO(A, B) (((A + B - 1) / B) * B) + +// device memory pitch alignment +static const size_t device_alignment = 32; + +// type traits +template struct traits; + +template <> struct traits { + // scalar type + typedef float T; + typedef T S; + + static constexpr T zero = 0.f; + static constexpr cudaDataType cuda_data_type = CUDA_R_32F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_R_32F; +#endif + + inline static S abs(T val) { return fabs(val); } + + template inline static T rand(RNG &gen) { return (S)gen(); } + + inline static T add(T a, T b) { return a + b; } + + inline static T mul(T v, S f) { return v * f; } +}; + +template <> struct traits { + // scalar type + typedef double T; + typedef T S; + + static constexpr T zero = 0.; + static constexpr cudaDataType cuda_data_type = CUDA_R_64F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_R_64F; +#endif + + inline static S abs(T val) { return fabs(val); } + + template inline static T rand(RNG &gen) { return (S)gen(); } + + inline static T add(T a, T b) { return a + b; } + + inline static T mul(T v, S f) { return v * f; } +}; + +template <> struct traits { + // scalar type + typedef float S; + typedef cuFloatComplex T; + + static constexpr T zero = {0.f, 0.f}; + static constexpr cudaDataType cuda_data_type = CUDA_C_32F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_C_32F; +#endif + + inline static S abs(T val) { return cuCabsf(val); } + + template inline static T rand(RNG &gen) { + return make_cuFloatComplex((S)gen(), (S)gen()); + } + + inline static T add(T a, T b) { return cuCaddf(a, b); } + inline static T add(T a, S b) { return cuCaddf(a, make_cuFloatComplex(b, 0.f)); } + + inline static T mul(T v, S f) { return make_cuFloatComplex(v.x * f, v.y * f); } +}; + +template <> struct traits { + // scalar type + typedef double S; + typedef cuDoubleComplex T; + + static constexpr T zero = {0., 0.}; + static constexpr cudaDataType cuda_data_type = CUDA_C_64F; +#if CUDART_VERSION >= 11000 + static constexpr cusolverPrecType_t cusolver_precision_type = CUSOLVER_C_64F; +#endif + + inline static S abs(T val) { return cuCabs(val); } + + template inline static T rand(RNG &gen) { + return make_cuDoubleComplex((S)gen(), (S)gen()); + } + + inline static T add(T a, T b) { return cuCadd(a, b); } + inline static T add(T a, S b) { return cuCadd(a, make_cuDoubleComplex(b, 0.)); } + + inline static T mul(T v, S f) { return make_cuDoubleComplex(v.x * f, v.y * f); } +}; + +template void print_matrix(const int &m, const int &n, const T *A, const int &lda); + +template <> void print_matrix(const int &m, const int &n, const float *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f ", A[j * lda + i]); + } + std::printf("\n"); + } +} + +template <> void print_matrix(const int &m, const int &n, const double *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f ", A[j * lda + i]); + } + std::printf("\n"); + } +} + +template <> void print_matrix(const int &m, const int &n, const cuComplex *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f + %0.2fj ", A[j * lda + i].x, A[j * lda + i].y); + } + std::printf("\n"); + } +} + +template <> +void print_matrix(const int &m, const int &n, const cuDoubleComplex *A, const int &lda) { + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + std::printf("%0.2f + %0.2fj ", A[j * lda + i].x, A[j * lda + i].y); + } + std::printf("\n"); + } +} + +template +void generate_random_matrix(cusolver_int_t m, cusolver_int_t n, T **A, int *lda) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution::S> dis(-1.0, 1.0); + auto rand_gen = std::bind(dis, gen); + + *lda = n; + + size_t matrix_mem_size = static_cast(*lda * m * sizeof(T)); + // suppress gcc 7 size warning + if (matrix_mem_size <= PTRDIFF_MAX) + *A = (T *)malloc(matrix_mem_size); + else + throw std::runtime_error("Memory allocation size is too large"); + + if (*A == NULL) + throw std::runtime_error("Unable to allocate host matrix"); + + for (int j = 0; j < n; ++j) { + for (int i = 0; i < m; ++i) { + T *A_col = (*A) + *lda * j; + A_col[i] = traits::rand(rand_gen); + } + } +} + +// Makes matrix A of size mxn and leading dimension lda diagonal dominant +template +void make_diag_dominant_matrix(cusolver_int_t m, cusolver_int_t n, T *A, int lda) { + for (int j = 0; j < std::min(m, n); ++j) { + T *A_col = A + lda * j; + auto col_sum = traits::S>::zero; + for (int i = 0; i < m; ++i) { + col_sum += traits::abs(A_col[i]); + } + A_col[j] = traits::add(A_col[j], col_sum); + } +} + +// Returns cudaDataType value as defined in library_types.h for the string containing type name +cudaDataType get_cuda_library_type(std::string type_string) { + if (type_string.compare("CUDA_R_16F") == 0) + return CUDA_R_16F; + else if (type_string.compare("CUDA_C_16F") == 0) + return CUDA_C_16F; + else if (type_string.compare("CUDA_R_32F") == 0) + return CUDA_R_32F; + else if (type_string.compare("CUDA_C_32F") == 0) + return CUDA_C_32F; + else if (type_string.compare("CUDA_R_64F") == 0) + return CUDA_R_64F; + else if (type_string.compare("CUDA_C_64F") == 0) + return CUDA_C_64F; + else if (type_string.compare("CUDA_R_8I") == 0) + return CUDA_R_8I; + else if (type_string.compare("CUDA_C_8I") == 0) + return CUDA_C_8I; + else if (type_string.compare("CUDA_R_8U") == 0) + return CUDA_R_8U; + else if (type_string.compare("CUDA_C_8U") == 0) + return CUDA_C_8U; + else if (type_string.compare("CUDA_R_32I") == 0) + return CUDA_R_32I; + else if (type_string.compare("CUDA_C_32I") == 0) + return CUDA_C_32I; + else if (type_string.compare("CUDA_R_32U") == 0) + return CUDA_R_32U; + else if (type_string.compare("CUDA_C_32U") == 0) + return CUDA_C_32U; + else + throw std::runtime_error("Unknown CUDA datatype"); +} + +// Returns cusolverIRSRefinement_t value as defined in cusolver_common.h for the string containing +// solver name +cusolverIRSRefinement_t get_cusolver_refinement_solver(std::string solver_string) { + if (solver_string.compare("CUSOLVER_IRS_REFINE_NONE") == 0) + return CUSOLVER_IRS_REFINE_NONE; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_CLASSICAL") == 0) + return CUSOLVER_IRS_REFINE_CLASSICAL; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_GMRES") == 0) + return CUSOLVER_IRS_REFINE_GMRES; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_CLASSICAL_GMRES") == 0) + return CUSOLVER_IRS_REFINE_CLASSICAL_GMRES; + else if (solver_string.compare("CUSOLVER_IRS_REFINE_GMRES_GMRES") == 0) + return CUSOLVER_IRS_REFINE_GMRES_GMRES; + else + printf("Unknown solver parameter: \"%s\"\n", solver_string.c_str()); + + return CUSOLVER_IRS_REFINE_NOT_SET; +} \ No newline at end of file diff --git a/src/cuda/H100/external/cugraph b/src/cuda/H100/external/cugraph new file mode 160000 index 000000000..15e04b307 --- /dev/null +++ b/src/cuda/H100/external/cugraph @@ -0,0 +1 @@ +Subproject commit 15e04b3070dda0985b281f69bc7d37ad0a91f94d diff --git a/src/cuda/H100/external/newton b/src/cuda/H100/external/newton new file mode 160000 index 000000000..141baffff --- /dev/null +++ b/src/cuda/H100/external/newton @@ -0,0 +1 @@ +Subproject commit 141baffff9d6059e94fbc399d9404868169555a8 diff --git a/src/cuda/H100/generate_graph.py b/src/cuda/H100/generate_graph.py new file mode 100755 index 000000000..0bc623ce3 --- /dev/null +++ b/src/cuda/H100/generate_graph.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Generate a synthetic graph with ~100K vertices for benchmarking. +Creates a scale-free graph using the Barabási-Albert model. +""" + +import sys +import random +import argparse + + +def create_barabasi_albert_graph(n, m, seed=42): + """ + Create a Barabási-Albert scale-free graph using optimized approach. + + Args: + n: Number of vertices + m: Number of edges to attach from a new node to existing nodes + seed: Random seed for reproducibility + + Returns: + List of edges (u, v) tuples + """ + random.seed(seed) + + print(f"Generating Barabási-Albert graph with {n:,} vertices...") + print(f"Each new node connects to {m} existing nodes") + + # Edge list + edges = [] + + # Targets for preferential attachment (repeating nodes based on degree) + # This allows O(1) random selection with degree-based probability + targets = [] + + # Start with a small complete graph + initial_nodes = max(m, 2) + for i in range(initial_nodes): + for j in range(i + 1, initial_nodes): + edges.append((i, j)) + targets.append(i) + targets.append(j) + + # Add remaining nodes with preferential attachment + for new_node in range(initial_nodes, n): + # Sample m unique nodes from targets (with replacement conceptually, + # but we ensure uniqueness) + selected = set() + + # Try to select m unique targets + attempts = 0 + while len(selected) < m and attempts < m * 20: + target = random.choice(targets) + selected.add(target) + attempts += 1 + + # If we couldn't get m unique targets (very unlikely), fill with any nodes + if len(selected) < m: + available = set(range(new_node)) - selected + needed = m - len(selected) + selected.update(random.sample(list(available), min(needed, len(available)))) + + # Add edges to selected nodes + for target in selected: + edges.append((new_node, target)) + # Add both endpoints to targets for preferential attachment + targets.append(new_node) + targets.append(target) + + # Progress indicator + if (new_node + 1) % 10000 == 0: + print(f" Generated {new_node + 1:,} / {n:,} vertices...") + + n_edges = len(edges) + print(f"Generated graph: {n:,} vertices, {n_edges:,} edges") + print(f"Average degree: {2 * n_edges / n:.2f}") + + return edges, n + + +def save_graph_as_mtx(edges, n_vertices, output_file): + """ + Save graph in Matrix Market (.mtx) format. + + Args: + edges: List of (u, v) tuples + n_vertices: Number of vertices + output_file: Output file path + """ + print(f"Saving to {output_file}...") + + # Make edges symmetric (undirected graph) and unique + all_edges = set() + for u, v in edges: + # Ensure u < v for consistent ordering + if u > v: + u, v = v, u + all_edges.add((u, v)) + + # Convert to sorted list + all_edges = sorted(all_edges) + + with open(output_file, 'w') as f: + # Write Matrix Market header + f.write("%%MatrixMarket matrix coordinate pattern symmetric\n") + f.write(f"% Barabási-Albert graph: {n_vertices} vertices, {len(all_edges)} edges\n") + + # Write dimensions: rows cols non-zeros + # For an adjacency matrix, we only store the upper triangle + f.write(f"{n_vertices} {n_vertices} {len(all_edges)}\n") + + # Write edges (1-based indexing in MTX format) + for u, v in all_edges: + f.write(f"{u+1} {v+1}\n") + + print(f"Successfully saved graph to {output_file}") + + # Print file size + import os + file_size = os.path.getsize(output_file) + if file_size < 1024*1024: + print(f"File size: {file_size/1024:.1f} KB") + else: + print(f"File size: {file_size/(1024*1024):.1f} MB") + + +def main(): + parser = argparse.ArgumentParser( + description='Generate a synthetic graph for benchmarking', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + '-n', '--vertices', + type=int, + default=100000, + help='Number of vertices' + ) + parser.add_argument( + '-m', '--edges-per-node', + type=int, + default=5, + help='Number of edges each new node attaches to (controls graph density)' + ) + parser.add_argument( + '-o', '--output', + type=str, + default='synthetic_100k.mtx', + help='Output file name' + ) + parser.add_argument( + '--seed', + type=int, + default=42, + help='Random seed for reproducibility' + ) + + args = parser.parse_args() + + # Validate arguments + if args.vertices < 1: + print("Error: Number of vertices must be positive") + sys.exit(1) + + if args.edges_per_node < 1 or args.edges_per_node >= args.vertices: + print(f"Error: edges-per-node must be between 1 and {args.vertices-1}") + sys.exit(1) + + # Generate graph + edges, n_vertices = create_barabasi_albert_graph( + args.vertices, + args.edges_per_node, + args.seed + ) + + # Save to MTX format + save_graph_as_mtx(edges, n_vertices, args.output) + + n_edges = len(edges) + print("\nGraph statistics:") + print(f" Vertices: {n_vertices:,}") + print(f" Edges: {n_edges:,}") + print(f" Average degree: {2 * n_edges / n_vertices:.2f}") + density = (2 * n_edges) / (n_vertices * (n_vertices - 1)) + print(f" Density: {density:.6f}") + + print(f"\nUsage:") + print(f" BFS: cd bfs && ./run.sh --file ../{args.output}") + print(f" MST: cd mst && ./run.sh --file ../{args.output}") + + +if __name__ == '__main__': + main() diff --git a/src/cuda/H100/get_dwt_data.sh b/src/cuda/H100/get_dwt_data.sh new file mode 100755 index 000000000..40e845a4e --- /dev/null +++ b/src/cuda/H100/get_dwt_data.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Generate random signal data for DWT + +DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/dwtHaar1D" +mkdir -p "$DATA_DIR" + +# Generate random signal files (using dd for portability - no numpy required) +for size in 512 1024 4096 16384 65536; do + dd if=/dev/urandom of="$DATA_DIR/signal_${size}.dat" bs=4 count=$size status=none 2>/dev/null + echo "Generated signal_${size}.dat" +done + +echo "DWT signal data ready" + +echo "DWT data ready in $DATA_DIR" diff --git a/src/cuda/H100/get_graph_data.sh b/src/cuda/H100/get_graph_data.sh new file mode 100755 index 000000000..7b2607891 --- /dev/null +++ b/src/cuda/H100/get_graph_data.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Download standard graph datasets and generate synthetic graph + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph" +mkdir -p "$DATA_DIR" + +# Download karate club (standard benchmark from SuiteSparse Matrix Collection) +if [ ! -f "$DATA_DIR/karate.mtx" ]; then + echo "Downloading karate.mtx..." + wget -O "$DATA_DIR/karate.tar.gz" \ + https://suitesparse-collection-website.herokuapp.com/MM/Newman/karate.tar.gz + cd "$DATA_DIR" && tar -xzf karate.tar.gz && mv karate/karate.mtx . && rm -rf karate karate.tar.gz +fi + +# Download netscience (standard benchmark from SuiteSparse Matrix Collection) +if [ ! -f "$DATA_DIR/netscience.mtx" ]; then + echo "Downloading netscience.mtx..." + wget -O "$DATA_DIR/netscience.tar.gz" \ + https://suitesparse-collection-website.herokuapp.com/MM/Newman/netscience.tar.gz + cd "$DATA_DIR" && tar -xzf netscience.tar.gz && mv netscience/netscience.mtx . && rm -rf netscience netscience.tar.gz +fi + +# Generate synthetic 100k vertex graph +if [ ! -f "$DATA_DIR/synthetic_100k.mtx" ]; then + echo "Generating synthetic_100k.mtx..." + python3 "$SCRIPT_DIR/generate_graph.py" -n 100000 -m 5 -o "$DATA_DIR/synthetic_100k.mtx" +fi + +echo "Graph data ready in $DATA_DIR" diff --git a/src/cuda/H100/get_image_data.sh b/src/cuda/H100/get_image_data.sh new file mode 100755 index 000000000..9e29c8fa4 --- /dev/null +++ b/src/cuda/H100/get_image_data.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Generate test images for recursiveGaussian + +DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian" +mkdir -p "$DATA_DIR" + +# Generate test PPM images using Python (portable, no ImageMagick dependency) +python3 - "$DATA_DIR" << 'EOF' +import os +import sys + +def create_gradient_ppm(filename, size): + """Create a simple gradient PPM image""" + with open(filename, 'w') as f: + f.write(f"P3\n{size} {size}\n255\n") + for y in range(size): + for x in range(size): + # Gradient from black to white + val = int((x + y) * 255 / (2 * size)) + f.write(f"{val} {val} {val} ") + f.write("\n") + +data_dir = os.path.expanduser(sys.argv[1]) +create_gradient_ppm(f"{data_dir}/teapot128.ppm", 128) +create_gradient_ppm(f"{data_dir}/teapot256.ppm", 256) +create_gradient_ppm(f"{data_dir}/teapot512.ppm", 512) +print(f"Generated test images in {data_dir}") +EOF + +echo "Image data ready in $DATA_DIR" diff --git a/src/cuda/H100/graph/bfs_standalone/CMakeLists.txt b/src/cuda/H100/graph/bfs_standalone/CMakeLists.txt new file mode 100644 index 000000000..478571f67 --- /dev/null +++ b/src/cuda/H100/graph/bfs_standalone/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.18) +project(bfs_standalone CUDA CXX) + +find_package(CUDAToolkit REQUIRED) + +# Try to find cuGraph installation +# First check environment variable, then common install locations +if(DEFINED ENV{CUGRAPH_ROOT}) + set(cugraph_ROOT $ENV{CUGRAPH_ROOT}) +elseif(EXISTS "$ENV{HOME}/cugraph") + set(cugraph_ROOT "$ENV{HOME}/cugraph") +else() + set(cugraph_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../external/cugraph") +endif() + +add_executable(bfs_standalone bfs_standalone.cu) +target_compile_features(bfs_standalone PRIVATE cxx_std_17) + +# Add include directories for cuGraph and dependencies +target_include_directories(bfs_standalone PRIVATE + ${cugraph_ROOT}/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/rmm-src/include + ${cugraph_ROOT}/cpp/build/_deps/raft-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/cuvs-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/rapids_logger-src/include +) + +# Link libraries +target_link_directories(bfs_standalone PRIVATE + ${cugraph_ROOT}/cpp/build + ${cugraph_ROOT}/cpp/build/_deps/rmm-build + ${cugraph_ROOT}/cpp/build/_deps/raft-build + ${cugraph_ROOT}/cpp/build/_deps/cuvs-build +) + +target_link_libraries(bfs_standalone PRIVATE + CUDA::cudart + CUDA::cublas + cugraph + cugraph_c +) diff --git a/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu b/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu new file mode 100644 index 000000000..1217ffe0f --- /dev/null +++ b/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu @@ -0,0 +1,259 @@ +/* + * Standalone BFS kernel extracted from cugraph test suite + * Simplified to run without gtest framework + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// Simple graph loader for MTX format +template +struct SimpleGraph { + std::vector offsets; + std::vector indices; + vertex_t num_vertices; + edge_t num_edges; +}; + +template +SimpleGraph load_mtx_graph(const std::string& filename) { + SimpleGraph graph; + + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Cannot open file " << filename << std::endl; + exit(1); + } + + // Check if graph is symmetric + bool is_symmetric = false; + std::string line; + while (std::getline(file, line)) { + if (line[0] != '%') break; + if (line.find("symmetric") != std::string::npos) { + is_symmetric = true; + } + } + + // Read dimensions + vertex_t num_rows, num_cols; + edge_t num_entries; + std::istringstream iss(line); + iss >> num_rows >> num_cols >> num_entries; + + graph.num_vertices = std::max(num_rows, num_cols); + + // Read edges + std::vector> edges; + vertex_t src, dst; + while (file >> src >> dst) { + src--; dst--; // MTX is 1-indexed + edges.push_back({src, dst}); + // For symmetric graphs, add reverse edge if not a self-loop + if (is_symmetric && src != dst) { + edges.push_back({dst, src}); + } + } + + graph.num_edges = edges.size(); + + // Convert to CSR format + graph.offsets.resize(graph.num_vertices + 1, 0); + + // Count degree + for (const auto& edge : edges) { + graph.offsets[edge.first + 1]++; + } + + // Prefix sum + for (vertex_t i = 0; i < graph.num_vertices; i++) { + graph.offsets[i + 1] += graph.offsets[i]; + } + + graph.indices.resize(edges.size()); + std::vector current_pos = graph.offsets; + + for (const auto& edge : edges) { + graph.indices[current_pos[edge.first]++] = edge.second; + } + + file.close(); + return graph; +} + +int main(int argc, char** argv) { + using vertex_t = int32_t; + using edge_t = int32_t; + using weight_t = float; + + // Parse command line arguments + std::string graph_file = "karate.mtx"; + vertex_t source = 0; + bool use_rmat = false; + int rmat_scale = 20; + int rmat_edge_factor = 16; + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg == "--file" && i + 1 < argc) { + graph_file = argv[++i]; + } else if (arg == "--source" && i + 1 < argc) { + source = std::atoi(argv[++i]); + } else if (arg == "--rmat") { + use_rmat = true; + } else if (arg == "--scale" && i + 1 < argc) { + rmat_scale = std::atoi(argv[++i]); + } else if (arg == "--edge-factor" && i + 1 < argc) { + rmat_edge_factor = std::atoi(argv[++i]); + } else if (arg == "--help" || arg == "-h") { + std::cout << "Usage: " << argv[0] << " [options]\n" + << "Options:\n" + << " --file Graph file in MTX format\n" + << " --source Source vertex for BFS (default: 0)\n" + << " --rmat Use RMAT generated graph instead of file\n" + << " --scale RMAT scale parameter (default: 20)\n" + << " --edge-factor RMAT edge factor (default: 16)\n" + << " --help, -h Show this help message\n"; + return 0; + } + } + + std::cout << "=== Standalone BFS Kernel ===" << std::endl; + std::cout << "Source vertex: " << source << std::endl; + + if (use_rmat) { + std::cout << "RMAT generation not supported in standalone version.\n"; + std::cout << "Please use --file option with an MTX graph file\n"; + return 1; + } + + std::cout << "Loading graph from: " << graph_file << std::endl; + + // Initialize RMM memory pool + auto cuda_mr = std::make_shared(); + auto pool_mr = std::make_shared>( + cuda_mr.get(), 1024 * 1024 * 1024ULL); // 1GB initial pool + rmm::mr::set_current_device_resource(pool_mr.get()); + + raft::handle_t handle{}; + HighResTimer hr_timer{}; + + // Load graph + auto h_graph = load_mtx_graph(graph_file); + std::cout << "Loaded graph: " << h_graph.num_vertices << " vertices, " + << h_graph.num_edges << " edges" << std::endl; + + // Copy edges to device + rmm::device_uvector d_src(h_graph.num_edges, handle.get_stream()); + rmm::device_uvector d_dst(h_graph.num_edges, handle.get_stream()); + + // Extract source and destination from indices/offsets + std::vector h_src, h_dst; + for (vertex_t v = 0; v < h_graph.num_vertices; v++) { + for (edge_t e = h_graph.offsets[v]; e < h_graph.offsets[v + 1]; e++) { + h_src.push_back(v); + h_dst.push_back(h_graph.indices[e]); + } + } + + cudaMemcpy(d_src.data(), h_src.data(), h_src.size() * sizeof(vertex_t), cudaMemcpyHostToDevice); + cudaMemcpy(d_dst.data(), h_dst.data(), h_dst.size() * sizeof(vertex_t), cudaMemcpyHostToDevice); + + std::cout << "Constructing cugraph..." << std::endl; + hr_timer.start("Graph construction"); + + // Build graph from edge list + std::optional> d_renumber_map{std::nullopt}; + + auto [graph, edge_properties, renumber_map] = + cugraph::create_graph_from_edgelist( + handle, + std::nullopt, // vertex list + std::move(d_src), + std::move(d_dst), + std::vector{}, // no edge properties + cugraph::graph_properties_t{true, false}, // undirected, no multi-edges + true); // renumber + + auto graph_view = graph.view(); + + RAFT_CUDA_TRY(cudaDeviceSynchronize()); + double graph_time = hr_timer.stop(); + std::cout << "Graph construction: " << (graph_time * 1000.0) << " ms" << std::endl; + + std::cout << "Running BFS from source " << source << "..." << std::endl; + + // Allocate output + rmm::device_uvector d_distances(graph_view.number_of_vertices(), handle.get_stream()); + rmm::device_uvector d_predecessors(graph_view.number_of_vertices(), handle.get_stream()); + + hr_timer.start("BFS"); + RAFT_CUDA_TRY(cudaDeviceSynchronize()); + + rmm::device_scalar const d_source(source, handle.get_stream()); + + cugraph::bfs(handle, + graph_view, + d_distances.data(), + d_predecessors.data(), + d_source.data(), + size_t{1}, + false, // direction optimizing (false for now) + std::numeric_limits::max()); + + RAFT_CUDA_TRY(cudaDeviceSynchronize()); + double bfs_time = hr_timer.stop(); + std::cout << "BFS execution: " << (bfs_time * 1000.0) << " ms" << std::endl; + + // Copy results back + std::vector h_distances(graph_view.number_of_vertices()); + std::vector h_predecessors(graph_view.number_of_vertices()); + + cudaMemcpy(h_distances.data(), d_distances.data(), + h_distances.size() * sizeof(vertex_t), cudaMemcpyDeviceToHost); + cudaMemcpy(h_predecessors.data(), d_predecessors.data(), + h_predecessors.size() * sizeof(vertex_t), cudaMemcpyDeviceToHost); + + // Print results summary + std::cout << "\n=== BFS Results ===" << std::endl; + std::cout << "First 10 vertices:" << std::endl; + std::cout << "Vertex\tDistance\tPredecessor" << std::endl; + for (int i = 0; i < std::min(10, (int)h_distances.size()); i++) { + std::cout << i << "\t" << h_distances[i] << "\t\t"; + if (h_predecessors[i] == cugraph::invalid_vertex_id::value) { + std::cout << "None"; + } else { + std::cout << h_predecessors[i]; + } + std::cout << std::endl; + } + + // Count reachable vertices + int reachable = 0; + for (auto d : h_distances) { + if (d != std::numeric_limits::max()) reachable++; + } + std::cout << "\nReachable vertices: " << reachable << " / " << h_distances.size() << std::endl; + + std::cout << "\n=== BFS Complete ===" << std::endl; + + return 0; +} diff --git a/src/cuda/H100/graph/mst_standalone/CMakeLists.txt b/src/cuda/H100/graph/mst_standalone/CMakeLists.txt new file mode 100644 index 000000000..f0c2dfb56 --- /dev/null +++ b/src/cuda/H100/graph/mst_standalone/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.18) +project(mst_standalone CUDA CXX) + +find_package(CUDAToolkit REQUIRED) + +# Try to find cuGraph installation +# First check environment variable, then common install locations +if(DEFINED ENV{CUGRAPH_ROOT}) + set(cugraph_ROOT $ENV{CUGRAPH_ROOT}) +elseif(EXISTS "$ENV{HOME}/cugraph") + set(cugraph_ROOT "$ENV{HOME}/cugraph") +else() + set(cugraph_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../external/cugraph") +endif() + +add_executable(mst_standalone mst_standalone.cu) +target_compile_features(mst_standalone PRIVATE cxx_std_17) + +# Add include directories for cuGraph and dependencies +target_include_directories(mst_standalone PRIVATE + ${cugraph_ROOT}/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/rmm-src/include + ${cugraph_ROOT}/cpp/build/_deps/raft-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/cuvs-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/rapids_logger-src/include +) + +# Link libraries +target_link_directories(mst_standalone PRIVATE + ${cugraph_ROOT}/cpp/build + ${cugraph_ROOT}/cpp/build/_deps/rmm-build + ${cugraph_ROOT}/cpp/build/_deps/raft-build + ${cugraph_ROOT}/cpp/build/_deps/cuvs-build +) + +target_link_libraries(mst_standalone PRIVATE + CUDA::cudart + CUDA::cublas + cugraph + cugraph_c +) diff --git a/src/cuda/H100/graph/mst_standalone/mst_standalone.cu b/src/cuda/H100/graph/mst_standalone/mst_standalone.cu new file mode 100644 index 000000000..c9d52f55d --- /dev/null +++ b/src/cuda/H100/graph/mst_standalone/mst_standalone.cu @@ -0,0 +1,244 @@ +/* + * Standalone MST (Minimum Spanning Tree) kernel + * Extracted and simplified from cugraph test suite + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +// Simple graph loader for MTX format +template +struct SimpleWeightedGraph { + std::vector row_indices; + std::vector col_indices; + std::vector weights; + vertex_t num_vertices; + edge_t num_edges; +}; + +template +SimpleWeightedGraph load_weighted_mtx_graph(const std::string& filename) { + SimpleWeightedGraph graph; + + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Cannot open file " << filename << std::endl; + exit(1); + } + + // Skip comments and read header + std::string line; + bool is_symmetric = false; + bool is_pattern = false; + + while (std::getline(file, line)) { + if (line[0] != '%') break; + if (line.find("symmetric") != std::string::npos) { + is_symmetric = true; + } + if (line.find("pattern") != std::string::npos) { + is_pattern = true; + } + } + + // Read dimensions from the first non-comment line + vertex_t num_rows, num_cols; + edge_t num_entries; + std::istringstream iss(line); + iss >> num_rows >> num_cols >> num_entries; + + graph.num_vertices = std::max(num_rows, num_cols); + + // Read edges + vertex_t src, dst; + weight_t weight; + + while (file >> src >> dst) { + src--; dst--; // MTX is 1-indexed + + if (is_pattern) { + weight = 1.0; // Default weight for pattern matrices + } else { + file >> weight; + } + + graph.row_indices.push_back(src); + graph.col_indices.push_back(dst); + graph.weights.push_back(weight); + + // For symmetric graphs, add reverse edge if not a self-loop + if (is_symmetric && src != dst) { + graph.row_indices.push_back(dst); + graph.col_indices.push_back(src); + graph.weights.push_back(weight); + } + } + + graph.num_edges = graph.row_indices.size(); + + file.close(); + return graph; +} + +// Convert COO to CSR format +template +struct CSRGraph { + std::vector offsets; + std::vector indices; + std::vector weights; + vertex_t num_vertices; + edge_t num_edges; +}; + +template +CSRGraph coo_to_csr(const SimpleWeightedGraph& coo) { + CSRGraph csr; + csr.num_vertices = coo.num_vertices; + csr.num_edges = coo.num_edges; + + // Initialize offsets + csr.offsets.resize(csr.num_vertices + 1, 0); + + // Count degree + for (const auto& src : coo.row_indices) { + csr.offsets[src + 1]++; + } + + // Prefix sum + for (vertex_t i = 0; i < csr.num_vertices; i++) { + csr.offsets[i + 1] += csr.offsets[i]; + } + + // Fill indices and weights + csr.indices.resize(coo.num_edges); + csr.weights.resize(coo.num_edges); + std::vector current_pos = csr.offsets; + + for (size_t i = 0; i < coo.row_indices.size(); i++) { + vertex_t src = coo.row_indices[i]; + edge_t pos = current_pos[src]++; + csr.indices[pos] = coo.col_indices[i]; + csr.weights[pos] = coo.weights[i]; + } + + return csr; +} + +int main(int argc, char** argv) { + using vertex_t = int32_t; + using edge_t = int32_t; + using weight_t = float; + + // Parse command line arguments + std::string graph_file = "graphs/karate.mtx"; + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg == "--file" && i + 1 < argc) { + graph_file = argv[++i]; + } else if (arg == "--help" || arg == "-h") { + std::cout << "Usage: " << argv[0] << " [options]\n" + << "Options:\n" + << " --file Graph file in MTX format (default: graphs/karate.mtx)\n" + << " --help, -h Show this help message\n"; + return 0; + } + } + + std::cout << "=== Standalone MST (Minimum Spanning Tree) Kernel ===" << std::endl; + std::cout << "Loading graph from: " << graph_file << std::endl; + + // Load graph + auto coo_graph = load_weighted_mtx_graph(graph_file); + std::cout << "Loaded graph: " << coo_graph.num_vertices << " vertices, " + << coo_graph.num_edges << " edges" << std::endl; + + // Convert to CSR + std::cout << "Converting to CSR format..." << std::endl; + auto csr_graph = coo_to_csr(coo_graph); + + // Initialize RMM memory resource + rmm::mr::cuda_memory_resource cuda_mr; + rmm::mr::pool_memory_resource pool_mr( + &cuda_mr, 512 * 1024 * 1024ULL); // 512 MB pool + rmm::mr::set_current_device_resource(&pool_mr); + + // Create RAFT handle + raft::handle_t handle; + + // Copy graph to device + std::cout << "Copying graph to device..." << std::endl; + rmm::device_uvector d_offsets(csr_graph.offsets.size(), handle.get_stream()); + rmm::device_uvector d_indices(csr_graph.indices.size(), handle.get_stream()); + rmm::device_uvector d_weights(csr_graph.weights.size(), handle.get_stream()); + + raft::update_device(d_offsets.data(), csr_graph.offsets.data(), + csr_graph.offsets.size(), handle.get_stream()); + raft::update_device(d_indices.data(), csr_graph.indices.data(), + csr_graph.indices.size(), handle.get_stream()); + raft::update_device(d_weights.data(), csr_graph.weights.data(), + csr_graph.weights.size(), handle.get_stream()); + + // Create cuGraph CSR view + std::cout << "Constructing cugraph..." << std::endl; + cugraph::legacy::GraphCSRView graph_view( + d_offsets.data(), + d_indices.data(), + d_weights.data(), + csr_graph.num_vertices, + csr_graph.num_edges); + + handle.sync_stream(); + + // Run MST + std::cout << "Running MST algorithm..." << std::endl; + + HighResTimer hr_timer{}; + hr_timer.start("MST"); + + auto mst_edges = cugraph::minimum_spanning_tree(handle, graph_view); + + handle.sync_stream(); + hr_timer.stop(); + + std::cout << "\n=== MST Results ===" << std::endl; + hr_timer.display_and_clear(std::cout); + + // Calculate MST weight + auto mst_weight = thrust::reduce( + thrust::device_pointer_cast(mst_edges->view().edge_data), + thrust::device_pointer_cast(mst_edges->view().edge_data) + mst_edges->view().number_of_edges); + + auto total_weight = thrust::reduce( + thrust::device_pointer_cast(d_weights.data()), + thrust::device_pointer_cast(d_weights.data()) + csr_graph.num_edges); + + std::cout << "MST edges: " << mst_edges->view().number_of_edges << std::endl; + std::cout << "MST total weight: " << mst_weight << std::endl; + std::cout << "Original graph total weight: " << total_weight << std::endl; + std::cout << "MST weight ratio: " << (mst_weight / total_weight * 100.0) << "%" << std::endl; + std::cout << "\nExpected MST edges for " << csr_graph.num_vertices + << " vertices: " << (csr_graph.num_vertices - 1) << std::endl; + + std::cout << "\n=== MST Complete ===" << std::endl; + + return 0; +} diff --git a/src/cuda/H100/image/FDTD3d/FDTD3d.cpp b/src/cuda/H100/image/FDTD3d/FDTD3d.cpp new file mode 100644 index 000000000..d05094055 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3d.cpp @@ -0,0 +1,232 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FDTD3d.h" + +#include +#include "helper_functions.h" +#include +#include +#include + +#include "FDTD3dGPU.h" +#include "FDTD3dReference.h" + +#ifndef CLAMP +#define CLAMP(a, min, max) (MIN(max, MAX(a, min))) +#endif + +//// Name of the log file +// const char *printfFile = "FDTD3d.txt"; + +// Forward declarations +bool runTest(int argc, const char **argv); +void showHelp(const int argc, const char **argv); + +int main(int argc, char **argv) +{ + bool bTestResult = false; + // Start the log + printf("%s Starting...\n\n", argv[0]); + + // Check help flag + if (checkCmdLineFlag(argc, (const char **)argv, "help")) { + printf("Displaying help on console\n"); + showHelp(argc, (const char **)argv); + bTestResult = true; + } + else { + // Execute + bTestResult = runTest(argc, (const char **)argv); + } + + // Finish + exit(bTestResult ? EXIT_SUCCESS : EXIT_FAILURE); +} + +void showHelp(const int argc, const char **argv) +{ + if (argc > 0) + std::cout << std::endl << argv[0] << std::endl; + + std::cout << std::endl << "Syntax:" << std::endl; + std::cout << std::left; + std::cout << " " << std::setw(20) << "--device=" + << "Specify device to use for execution" << std::endl; + std::cout << " " << std::setw(20) << "--dimx=" + << "Specify number of elements in x direction (excluding halo)" << std::endl; + std::cout << " " << std::setw(20) << "--dimy=" + << "Specify number of elements in y direction (excluding halo)" << std::endl; + std::cout << " " << std::setw(20) << "--dimz=" + << "Specify number of elements in z direction (excluding halo)" << std::endl; + std::cout << " " << std::setw(20) << "--radius=" + << "Specify radius of stencil" << std::endl; + std::cout << " " << std::setw(20) << "--timesteps=" + << "Specify number of timesteps" << std::endl; + std::cout << " " << std::setw(20) << "--block-size=" + << "Specify number of threads per block" << std::endl; + std::cout << std::endl; + std::cout << " " << std::setw(20) << "--noprompt" + << "Skip prompt before exit" << std::endl; + std::cout << std::endl; +} + +bool runTest(int argc, const char **argv) +{ + float *host_output; + float *device_output; + float *input; + float *coeff; + + int defaultDim; + int dimx; + int dimy; + int dimz; + int outerDimx; + int outerDimy; + int outerDimz; + int radius; + int timesteps; + size_t volumeSize; + memsize_t memsize; + + const float lowerBound = 0.0f; + const float upperBound = 1.0f; + + // Determine default dimensions + printf("Set-up, based upon target device GMEM size...\n"); + // Get the memory size of the target device + printf(" getTargetDeviceGlobalMemSize\n"); + getTargetDeviceGlobalMemSize(&memsize, argc, argv); + + // We can never use all the memory so to keep things simple we aim to + // use around half the total memory + memsize /= 2; + + // Most of our memory use is taken up by the input and output buffers - + // two buffers of equal size - and for simplicity the volume is a cube: + // dim = floor( (N/2)^(1/3) ) + defaultDim = (int)floor(pow((memsize / (2.0 * sizeof(float))), 1.0 / 3.0)); + + // By default, make the volume edge size an integer multiple of 128B to + // improve performance by coalescing memory accesses, in a real + // application it would make sense to pad the lines accordingly + int roundTarget = 128 / sizeof(float); + defaultDim = defaultDim / roundTarget * roundTarget; + defaultDim -= k_radius_default * 2; + + // Check dimension is valid + if (defaultDim < k_dim_min) { + printf("insufficient device memory (maximum volume on device is %d, must be " + "between %d and %d).\n", + defaultDim, + k_dim_min, + k_dim_max); + exit(EXIT_FAILURE); + } + else if (defaultDim > k_dim_max) { + defaultDim = k_dim_max; + } + + // For QA testing, override default volume size + if (checkCmdLineFlag(argc, argv, "qatest")) { + defaultDim = MIN(defaultDim, k_dim_qa); + } + + // set default dim + dimx = defaultDim; + dimy = defaultDim; + dimz = defaultDim; + radius = k_radius_default; + timesteps = k_timesteps_default; + + // Parse command line arguments + if (checkCmdLineFlag(argc, argv, "dimx")) { + dimx = CLAMP(getCmdLineArgumentInt(argc, argv, "dimx"), k_dim_min, k_dim_max); + } + + if (checkCmdLineFlag(argc, argv, "dimy")) { + dimy = CLAMP(getCmdLineArgumentInt(argc, argv, "dimy"), k_dim_min, k_dim_max); + } + + if (checkCmdLineFlag(argc, argv, "dimz")) { + dimz = CLAMP(getCmdLineArgumentInt(argc, argv, "dimz"), k_dim_min, k_dim_max); + } + + if (checkCmdLineFlag(argc, argv, "radius")) { + radius = CLAMP(getCmdLineArgumentInt(argc, argv, "radius"), k_radius_min, k_radius_max); + } + + if (checkCmdLineFlag(argc, argv, "timesteps")) { + timesteps = CLAMP(getCmdLineArgumentInt(argc, argv, "timesteps"), k_timesteps_min, k_timesteps_max); + } + + // Determine volume size + outerDimx = dimx + 2 * radius; + outerDimy = dimy + 2 * radius; + outerDimz = dimz + 2 * radius; + volumeSize = outerDimx * outerDimy * outerDimz; + + // Allocate memory + host_output = (float *)calloc(volumeSize, sizeof(float)); + input = (float *)malloc(volumeSize * sizeof(float)); + coeff = (float *)malloc((radius + 1) * sizeof(float)); + + // Create coefficients + for (int i = 0; i <= radius; i++) { + coeff[i] = 0.1f; + } + + // Generate data + printf(" generateRandomData\n\n"); + generateRandomData(input, outerDimx, outerDimy, outerDimz, lowerBound, upperBound); + printf("FDTD on %d x %d x %d volume with symmetric filter radius %d for %d " + "timesteps...\n\n", + dimx, + dimy, + dimz, + radius, + timesteps); + + // Execute on the host + printf("fdtdReference...\n"); + fdtdReference(host_output, input, coeff, dimx, dimy, dimz, radius, timesteps); + printf("fdtdReference complete\n"); + + // Allocate memory + device_output = (float *)calloc(volumeSize, sizeof(float)); + + // Execute on the device + printf("fdtdGPU...\n"); + fdtdGPU(device_output, input, coeff, dimx, dimy, dimz, radius, timesteps, argc, argv); + printf("fdtdGPU complete\n"); + + // Compare the results + float tolerance = 0.0001f; + printf("\nCompareData (tolerance %f)...\n", tolerance); + return compareData(device_output, host_output, dimx, dimy, dimz, radius, tolerance); +} diff --git a/src/cuda/H100/image/FDTD3d/FDTD3d.h b/src/cuda/H100/image/FDTD3d/FDTD3d.h new file mode 100644 index 000000000..bcab438f7 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3d.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FDTD3D_H_ +#define _FDTD3D_H_ + +// The values are set to give reasonable runtimes, they can +// be changed but note that running very large dimensions can +// take a very long time and you should avoid running on your +// primary display in this case. +#define k_dim_min 96 +#define k_dim_max 512 +#define k_dim_qa 248 + +// Note that the radius is defined here as exactly 4 since the +// kernel code uses a constant. If you want a different radius +// you must change the kernel accordingly. +#define k_radius_min 4 +#define k_radius_max 4 +#define k_radius_default 4 + +// The values are set to give reasonable runtimes, they can +// be changed but note that running a very large number of +// timesteps can take a very long time and you should avoid +// running on your primary display in this case. +#define k_timesteps_min 1 +#define k_timesteps_max 10 +#define k_timesteps_default 5 + +#endif diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu b/src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu new file mode 100644 index 000000000..6dc2caf35 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu @@ -0,0 +1,260 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "helper_cuda.h" +#include "helper_functions.h" +#include + +#include "FDTD3dGPU.h" +#include "FDTD3dGPUKernel.cuh" + +bool getTargetDeviceGlobalMemSize(memsize_t *result, const int argc, const char **argv) +{ + int deviceCount = 0; + int targetDevice = 0; + size_t memsize = 0; + + // Get the number of CUDA enabled GPU devices + printf(" cudaGetDeviceCount\n"); + checkCudaErrors(cudaGetDeviceCount(&deviceCount)); + + // Select target device (device 0 by default) + targetDevice = findCudaDevice(argc, (const char **)argv); + + // Query target device for maximum memory allocation + printf(" cudaGetDeviceProperties\n"); + struct cudaDeviceProp deviceProp; + checkCudaErrors(cudaGetDeviceProperties(&deviceProp, targetDevice)); + + memsize = deviceProp.totalGlobalMem; + + // Save the result + *result = (memsize_t)memsize; + return true; +} + +bool fdtdGPU(float *output, + const float *input, + const float *coeff, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const int timesteps, + const int argc, + const char **argv) +{ + const int outerDimx = dimx + 2 * radius; + const int outerDimy = dimy + 2 * radius; + const int outerDimz = dimz + 2 * radius; + const size_t volumeSize = outerDimx * outerDimy * outerDimz; + int deviceCount = 0; + int targetDevice = 0; + float *bufferOut = 0; + float *bufferIn = 0; + dim3 dimBlock; + dim3 dimGrid; + + // Ensure that the inner data starts on a 128B boundary + const int padding = (128 / sizeof(float)) - radius; + const size_t paddedVolumeSize = volumeSize + padding; + +#ifdef GPU_PROFILING + cudaEvent_t profileStart = 0; + cudaEvent_t profileEnd = 0; + const int profileTimesteps = timesteps - 1; + + if (profileTimesteps < 1) { + printf(" cannot profile with fewer than two timesteps (timesteps=%d), " + "profiling is disabled.\n", + timesteps); + } + +#endif + + // Check the radius is valid + if (radius != RADIUS) { + printf("radius is invalid, must be %d - see kernel for details.\n", RADIUS); + exit(EXIT_FAILURE); + } + + // Get the number of CUDA enabled GPU devices + checkCudaErrors(cudaGetDeviceCount(&deviceCount)); + + // Select target device (device 0 by default) + targetDevice = findCudaDevice(argc, (const char **)argv); + + checkCudaErrors(cudaSetDevice(targetDevice)); + + // Allocate memory buffers + checkCudaErrors(cudaMalloc((void **)&bufferOut, paddedVolumeSize * sizeof(float))); + checkCudaErrors(cudaMalloc((void **)&bufferIn, paddedVolumeSize * sizeof(float))); + + // Check for a command-line specified block size + int userBlockSize; + + if (checkCmdLineFlag(argc, (const char **)argv, "block-size")) { + userBlockSize = getCmdLineArgumentInt(argc, argv, "block-size"); + // Constrain to a multiple of k_blockDimX + userBlockSize = (userBlockSize / k_blockDimX * k_blockDimX); + + // Constrain within allowed bounds + userBlockSize = MIN(MAX(userBlockSize, k_blockSizeMin), k_blockSizeMax); + } + else { + userBlockSize = k_blockSizeMax; + } + + // Check the device limit on the number of threads + struct cudaFuncAttributes funcAttrib; + checkCudaErrors(cudaFuncGetAttributes(&funcAttrib, FiniteDifferencesKernel)); + + userBlockSize = MIN(userBlockSize, funcAttrib.maxThreadsPerBlock); + + // Set the block size + dimBlock.x = k_blockDimX; + // Visual Studio 2005 does not like std::min + // dimBlock.y = std::min(userBlockSize / k_blockDimX, + // (size_t)k_blockDimMaxY); + dimBlock.y = ((userBlockSize / k_blockDimX) < (size_t)k_blockDimMaxY) ? (userBlockSize / k_blockDimX) + : (size_t)k_blockDimMaxY; + dimGrid.x = (unsigned int)ceil((float)dimx / dimBlock.x); + dimGrid.y = (unsigned int)ceil((float)dimy / dimBlock.y); + printf(" set block size to %dx%d\n", dimBlock.x, dimBlock.y); + printf(" set grid size to %dx%d\n", dimGrid.x, dimGrid.y); + + // Check the block size is valid + if (dimBlock.x < RADIUS || dimBlock.y < RADIUS) { + printf("invalid block size, x (%d) and y (%d) must be >= radius (%d).\n", dimBlock.x, dimBlock.y, RADIUS); + exit(EXIT_FAILURE); + } + + // Copy the input to the device input buffer + checkCudaErrors(cudaMemcpy(bufferIn + padding, input, volumeSize * sizeof(float), cudaMemcpyHostToDevice)); + + // Copy the input to the device output buffer (actually only need the halo) + checkCudaErrors(cudaMemcpy(bufferOut + padding, input, volumeSize * sizeof(float), cudaMemcpyHostToDevice)); + + // Copy the coefficients to the device coefficient buffer + checkCudaErrors(cudaMemcpyToSymbol(stencil, (void *)coeff, (radius + 1) * sizeof(float))); + +#ifdef GPU_PROFILING + + // Create the events + checkCudaErrors(cudaEventCreate(&profileStart)); + checkCudaErrors(cudaEventCreate(&profileEnd)); + +#endif + + // Execute the FDTD + float *bufferSrc = bufferIn + padding; + float *bufferDst = bufferOut + padding; + printf(" GPU FDTD loop\n"); + +#ifdef GPU_PROFILING + // Enqueue start event + checkCudaErrors(cudaEventRecord(profileStart, 0)); +#endif + + for (int it = 0; it < timesteps; it++) { + printf("\tt = %d ", it); + + // Launch the kernel + printf("launch kernel\n"); + FiniteDifferencesKernel<<>>(bufferDst, bufferSrc, dimx, dimy, dimz); + + // Toggle the buffers + // Visual Studio 2005 does not like std::swap + // std::swap(bufferSrc, bufferDst); + float *tmp = bufferDst; + bufferDst = bufferSrc; + bufferSrc = tmp; + } + + printf("\n"); + +#ifdef GPU_PROFILING + // Enqueue end event + checkCudaErrors(cudaEventRecord(profileEnd, 0)); +#endif + + // Wait for the kernel to complete + checkCudaErrors(cudaDeviceSynchronize()); + + // Read the result back, result is in bufferSrc (after final toggle) + checkCudaErrors(cudaMemcpy(output, bufferSrc, volumeSize * sizeof(float), cudaMemcpyDeviceToHost)); + +// Report time +#ifdef GPU_PROFILING + float elapsedTimeMS = 0; + + if (profileTimesteps > 0) { + checkCudaErrors(cudaEventElapsedTime(&elapsedTimeMS, profileStart, profileEnd)); + } + + if (profileTimesteps > 0) { + // Convert milliseconds to seconds + double elapsedTime = elapsedTimeMS * 1.0e-3; + double avgElapsedTime = elapsedTime / (double)profileTimesteps; + // Determine number of computations per timestep + size_t pointsComputed = dimx * dimy * dimz; + // Determine throughput + double throughputM = 1.0e-6 * (double)pointsComputed / avgElapsedTime; + printf("FDTD3d, Throughput = %.4f MPoints/s, Time = %.5f s, Size = %u Points, " + "NumDevsUsed = %u, Blocksize = %u\n", + throughputM, + avgElapsedTime, + pointsComputed, + 1, + dimBlock.x * dimBlock.y); + } + +#endif + + // Cleanup + if (bufferIn) { + checkCudaErrors(cudaFree(bufferIn)); + } + + if (bufferOut) { + checkCudaErrors(cudaFree(bufferOut)); + } + +#ifdef GPU_PROFILING + + if (profileStart) { + checkCudaErrors(cudaEventDestroy(profileStart)); + } + + if (profileEnd) { + checkCudaErrors(cudaEventDestroy(profileEnd)); + } + +#endif + return true; +} diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPU.h b/src/cuda/H100/image/FDTD3d/FDTD3dGPU.h new file mode 100644 index 000000000..463f7e1d9 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3dGPU.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FDTD3DGPU_H_ +#define _FDTD3DGPU_H_ + +#include +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) && defined(_MSC_VER) +typedef unsigned __int64 memsize_t; +#else +#include +typedef uint64_t memsize_t; +#endif + +#define k_blockDimX 32 +#define k_blockDimMaxY 16 +#define k_blockSizeMin 128 +#define k_blockSizeMax (k_blockDimX * k_blockDimMaxY) + +bool getTargetDeviceGlobalMemSize(memsize_t *result, const int argc, const char **argv); +bool fdtdGPU(float *output, + const float *input, + const float *coeff, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const int timesteps, + const int argc, + const char **argv); + +#endif diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh b/src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh new file mode 100644 index 000000000..c6a473559 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh @@ -0,0 +1,160 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "FDTD3dGPU.h" + +namespace cg = cooperative_groups; + +// Note: If you change the RADIUS, you should also change the unrolling below +#define RADIUS 4 + +__constant__ float stencil[RADIUS + 1]; + +__global__ void +FiniteDifferencesKernel(float *output, const float *input, const int dimx, const int dimy, const int dimz) +{ + bool validr = true; + bool validw = true; + const int gtidx = blockIdx.x * blockDim.x + threadIdx.x; + const int gtidy = blockIdx.y * blockDim.y + threadIdx.y; + const int ltidx = threadIdx.x; + const int ltidy = threadIdx.y; + const int workx = blockDim.x; + const int worky = blockDim.y; + // Handle to thread block group + cg::thread_block cta = cg::this_thread_block(); + __shared__ float tile[k_blockDimMaxY + 2 * RADIUS][k_blockDimX + 2 * RADIUS]; + + const int stride_y = dimx + 2 * RADIUS; + const int stride_z = stride_y * (dimy + 2 * RADIUS); + + int inputIndex = 0; + int outputIndex = 0; + + // Advance inputIndex to start of inner volume + inputIndex += RADIUS * stride_y + RADIUS; + + // Advance inputIndex to target element + inputIndex += gtidy * stride_y + gtidx; + + float infront[RADIUS]; + float behind[RADIUS]; + float current; + + const int tx = ltidx + RADIUS; + const int ty = ltidy + RADIUS; + + // Check in bounds + if ((gtidx >= dimx + RADIUS) || (gtidy >= dimy + RADIUS)) + validr = false; + + if ((gtidx >= dimx) || (gtidy >= dimy)) + validw = false; + + // Preload the "infront" and "behind" data + for (int i = RADIUS - 2; i >= 0; i--) { + if (validr) + behind[i] = input[inputIndex]; + + inputIndex += stride_z; + } + + if (validr) + current = input[inputIndex]; + + outputIndex = inputIndex; + inputIndex += stride_z; + + for (int i = 0; i < RADIUS; i++) { + if (validr) + infront[i] = input[inputIndex]; + + inputIndex += stride_z; + } + +// Step through the xy-planes +#pragma unroll 9 + + for (int iz = 0; iz < dimz; iz++) { + // Advance the slice (move the thread-front) + for (int i = RADIUS - 1; i > 0; i--) + behind[i] = behind[i - 1]; + + behind[0] = current; + current = infront[0]; +#pragma unroll 4 + + for (int i = 0; i < RADIUS - 1; i++) + infront[i] = infront[i + 1]; + + if (validr) + infront[RADIUS - 1] = input[inputIndex]; + + inputIndex += stride_z; + outputIndex += stride_z; + cg::sync(cta); + + // Note that for the work items on the boundary of the problem, the + // supplied index when reading the halo (below) may wrap to the + // previous/next row or even the previous/next xy-plane. This is + // acceptable since a) we disable the output write for these work + // items and b) there is at least one xy-plane before/after the + // current plane, so the access will be within bounds. + + // Update the data slice in the local tile + // Halo above & below + if (ltidy < RADIUS) { + tile[ltidy][tx] = input[outputIndex - RADIUS * stride_y]; + tile[ltidy + worky + RADIUS][tx] = input[outputIndex + worky * stride_y]; + } + + // Halo left & right + if (ltidx < RADIUS) { + tile[ty][ltidx] = input[outputIndex - RADIUS]; + tile[ty][ltidx + workx + RADIUS] = input[outputIndex + workx]; + } + + tile[ty][tx] = current; + cg::sync(cta); + + // Compute the output value + float value = stencil[0] * current; +#pragma unroll 4 + + for (int i = 1; i <= RADIUS; i++) { + value += stencil[i] + * (infront[i - 1] + behind[i - 1] + tile[ty - i][tx] + tile[ty + i][tx] + tile[ty][tx - i] + + tile[ty][tx + i]); + } + + // Store the output value + if (validw) + output[outputIndex] = value; + } +} diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp b/src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp new file mode 100644 index 000000000..6ece3469c --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp @@ -0,0 +1,191 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FDTD3dReference.h" + +#include +#include +#include +#include +#include + +void generateRandomData(float *data, + const int dimx, + const int dimy, + const int dimz, + const float lowerBound, + const float upperBound) +{ + srand(0); + + for (int iz = 0; iz < dimz; iz++) { + for (int iy = 0; iy < dimy; iy++) { + for (int ix = 0; ix < dimx; ix++) { + *data = (float)(lowerBound + ((float)rand() / (float)RAND_MAX) * (upperBound - lowerBound)); + ++data; + } + } + } +} + +void generatePatternData(float *data, + const int dimx, + const int dimy, + const int dimz, + const float lowerBound, + const float upperBound) +{ + for (int iz = 0; iz < dimz; iz++) { + for (int iy = 0; iy < dimy; iy++) { + for (int ix = 0; ix < dimx; ix++) { + *data = (float)(lowerBound + ((float)iz / (float)dimz) * (upperBound - lowerBound)); + ++data; + } + } + } +} + +bool fdtdReference(float *output, + const float *input, + const float *coeff, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const int timesteps) +{ + const int outerDimx = dimx + 2 * radius; + const int outerDimy = dimy + 2 * radius; + const int outerDimz = dimz + 2 * radius; + const size_t volumeSize = outerDimx * outerDimy * outerDimz; + const int stride_y = outerDimx; + const int stride_z = stride_y * outerDimy; + float *intermediate = 0; + const float *bufsrc = 0; + float *bufdst = 0; + float *bufdstnext = 0; + + // Allocate temporary buffer + printf(" calloc intermediate\n"); + intermediate = (float *)calloc(volumeSize, sizeof(float)); + + // Decide which buffer to use first (result should end up in output) + if ((timesteps % 2) == 0) { + bufsrc = input; + bufdst = intermediate; + bufdstnext = output; + } + else { + bufsrc = input; + bufdst = output; + bufdstnext = intermediate; + } + + // Run the FDTD (naive method) + printf(" Host FDTD loop\n"); + + for (int it = 0; it < timesteps; it++) { + printf("\tt = %d\n", it); + const float *src = bufsrc; + float *dst = bufdst; + + for (int iz = -radius; iz < dimz + radius; iz++) { + for (int iy = -radius; iy < dimy + radius; iy++) { + for (int ix = -radius; ix < dimx + radius; ix++) { + if (ix >= 0 && ix < dimx && iy >= 0 && iy < dimy && iz >= 0 && iz < dimz) { + float value = (*src) * coeff[0]; + + for (int ir = 1; ir <= radius; ir++) { + value += coeff[ir] * (*(src + ir) + *(src - ir)); // horizontal + value += coeff[ir] * (*(src + ir * stride_y) + *(src - ir * stride_y)); // vertical + value += coeff[ir] * (*(src + ir * stride_z) + *(src - ir * stride_z)); // in front & behind + } + + *dst = value; + } + else { + *dst = *src; + } + + ++dst; + ++src; + } + } + } + + // Rotate buffers + float *tmp = bufdst; + bufdst = bufdstnext; + bufdstnext = tmp; + bufsrc = (const float *)tmp; + } + + printf("\n"); + + if (intermediate) + free(intermediate); + + return true; +} + +bool compareData(const float *output, + const float *reference, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const float tolerance) +{ + for (int iz = -radius; iz < dimz + radius; iz++) { + for (int iy = -radius; iy < dimy + radius; iy++) { + for (int ix = -radius; ix < dimx + radius; ix++) { + if (ix >= 0 && ix < dimx && iy >= 0 && iy < dimy && iz >= 0 && iz < dimz) { + // Determine the absolute difference + float difference = fabs(*reference - *output); + float error; + + // Determine the relative error + if (*reference != 0) + error = difference / *reference; + else + error = difference; + + // Check the error is within the tolerance + if (error > tolerance) { + printf("Data error at point (%d,%d,%d)\t%f instead of %f\n", ix, iy, iz, *output, *reference); + return false; + } + } + + ++output; + ++reference; + } + } + } + + return true; +} diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dReference.h b/src/cuda/H100/image/FDTD3d/FDTD3dReference.h new file mode 100644 index 000000000..f39ad62be --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/FDTD3dReference.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FDTD3DREFERENCE_H_ +#define _FDTD3DREFERENCE_H_ + +void generateRandomData(float *data, + const int dimx, + const int dimy, + const int dimz, + const float lowerBound, + const float upperBound); +void generatePatternData(float *data, + const int dimx, + const int dimy, + const int dimz, + const float lowerBound, + const float upperBound); +bool fdtdReference(float *output, + const float *input, + const float *coeff, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const int timesteps); +bool compareData(const float *output, + const float *reference, + const int dimx, + const int dimy, + const int dimz, + const int radius, + const float tolerance = 0.0001f); + +#endif diff --git a/src/cuda/H100/image/FDTD3d/Makefile b/src/cuda/H100/image/FDTD3d/Makefile new file mode 100644 index 000000000..12ed30fc3 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/Makefile @@ -0,0 +1,8 @@ +EXECUTABLE := FDTD3d +CUFILES := FDTD3dGPU.cu +CCFILES := FDTD3d.cpp FDTD3dReference.cpp +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +include ../../../common/common.mk diff --git a/src/cuda/H100/image/FDTD3d/exception.h b/src/cuda/H100/image/FDTD3d/exception.h new file mode 100644 index 000000000..ca8ac2525 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/exception.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* CUda UTility Library */ +#ifndef COMMON_EXCEPTION_H_ +#define COMMON_EXCEPTION_H_ + +// includes, system +#include +#include +#include +#include +#include + +//! Exception wrapper. +//! @param Std_Exception Exception out of namespace std for easy typing. +template +class Exception : public Std_Exception { + public: + //! @brief Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const char *detailed = "-"); + + //! Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const std::string &detailed); + + //! Destructor + virtual ~Exception() throw(); + + private: + //! Constructor, default (private) + Exception(); + + //! Constructor, standard + //! @param str string returned by what() + explicit Exception(const std::string &str); +}; + +//////////////////////////////////////////////////////////////////////////////// +//! Exception handler function for arbitrary exceptions +//! @param ex exception to handle +//////////////////////////////////////////////////////////////////////////////// +template +inline void handleException(const Exception_Typ &ex) { + std::cerr << ex.what() << std::endl; + + exit(EXIT_FAILURE); +} + +//! Convenience macros + +//! Exception caused by dynamic program behavior, e.g. file does not exist +#define RUNTIME_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Logic exception in program, e.g. an assert failed +#define LOGIC_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Out of range exception +#define RANGE_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//////////////////////////////////////////////////////////////////////////////// +//! Implementation + +// includes, system +#include + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const char *detailed) { + std::stringstream s; + + // Quiet heavy-weight but exceptions are not for + // performance / release versions + s << "Exception in file '" << file << "' in line " << line << "\n" + << "Detailed description: " << detailed << "\n"; + + throw Exception(s.str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const std::string &msg) { + throw_it(file, line, msg.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, default (private). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception() : Std_Exception("Unknown Exception.\n") {} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, standard (private). +//! String returned by what(). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception(const std::string &s) : Std_Exception(s) {} + +//////////////////////////////////////////////////////////////////////////////// +//! Destructor +//////////////////////////////////////////////////////////////////////////////// +template +Exception::~Exception() throw() {} + + // functions, exported + +#endif // COMMON_EXCEPTION_H_ diff --git a/src/cuda/H100/image/FDTD3d/helper_cuda.h b/src/cuda/H100/image/FDTD3d/helper_cuda.h new file mode 100644 index 000000000..723b28f7a --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/helper_cuda.h @@ -0,0 +1,988 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//////////////////////////////////////////////////////////////////////////////// +// These are CUDA Helper functions for initialization and error checking + +#ifndef COMMON_HELPER_CUDA_H_ +#define COMMON_HELPER_CUDA_H_ + +#pragma once + +#include +#include +#include +#include + +#include "helper_string.h" + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// Note, it is required that your SDK sample to include the proper header +// files, please refer the CUDA examples for examples of the needed CUDA +// headers, which may change depending on which CUDA functions are used. + +// CUDA Runtime error messages +#ifdef __DRIVER_TYPES_H__ +static const char *_cudaGetErrorEnum(cudaError_t error) { + return cudaGetErrorName(error); +} +#endif + +#ifdef CUDA_DRIVER_API +// CUDA Driver API errors +static const char *_cudaGetErrorEnum(CUresult error) { + static char unknown[] = ""; + const char *ret = NULL; + cuGetErrorName(error, &ret); + return ret ? ret : unknown; +} +#endif + +#ifdef CUBLAS_API_H_ +// cuBLAS API errors +static const char *_cudaGetErrorEnum(cublasStatus_t error) { + switch (error) { + case CUBLAS_STATUS_SUCCESS: + return "CUBLAS_STATUS_SUCCESS"; + + case CUBLAS_STATUS_NOT_INITIALIZED: + return "CUBLAS_STATUS_NOT_INITIALIZED"; + + case CUBLAS_STATUS_ALLOC_FAILED: + return "CUBLAS_STATUS_ALLOC_FAILED"; + + case CUBLAS_STATUS_INVALID_VALUE: + return "CUBLAS_STATUS_INVALID_VALUE"; + + case CUBLAS_STATUS_ARCH_MISMATCH: + return "CUBLAS_STATUS_ARCH_MISMATCH"; + + case CUBLAS_STATUS_MAPPING_ERROR: + return "CUBLAS_STATUS_MAPPING_ERROR"; + + case CUBLAS_STATUS_EXECUTION_FAILED: + return "CUBLAS_STATUS_EXECUTION_FAILED"; + + case CUBLAS_STATUS_INTERNAL_ERROR: + return "CUBLAS_STATUS_INTERNAL_ERROR"; + + case CUBLAS_STATUS_NOT_SUPPORTED: + return "CUBLAS_STATUS_NOT_SUPPORTED"; + + case CUBLAS_STATUS_LICENSE_ERROR: + return "CUBLAS_STATUS_LICENSE_ERROR"; + } + + return ""; +} +#endif + +#ifdef _CUFFT_H_ +// cuFFT API errors +static const char *_cudaGetErrorEnum(cufftResult error) { + switch (error) { + case CUFFT_SUCCESS: + return "CUFFT_SUCCESS"; + + case CUFFT_INVALID_PLAN: + return "CUFFT_INVALID_PLAN"; + + case CUFFT_ALLOC_FAILED: + return "CUFFT_ALLOC_FAILED"; + + case CUFFT_INVALID_TYPE: + return "CUFFT_INVALID_TYPE"; + + case CUFFT_INVALID_VALUE: + return "CUFFT_INVALID_VALUE"; + + case CUFFT_INTERNAL_ERROR: + return "CUFFT_INTERNAL_ERROR"; + + case CUFFT_EXEC_FAILED: + return "CUFFT_EXEC_FAILED"; + + case CUFFT_SETUP_FAILED: + return "CUFFT_SETUP_FAILED"; + + case CUFFT_INVALID_SIZE: + return "CUFFT_INVALID_SIZE"; + + case CUFFT_UNALIGNED_DATA: + return "CUFFT_UNALIGNED_DATA"; + + case CUFFT_INVALID_DEVICE: + return "CUFFT_INVALID_DEVICE"; + + case CUFFT_NO_WORKSPACE: + return "CUFFT_NO_WORKSPACE"; + + case CUFFT_NOT_IMPLEMENTED: + return "CUFFT_NOT_IMPLEMENTED"; + + case CUFFT_NOT_SUPPORTED: + return "CUFFT_NOT_SUPPORTED"; + + case CUFFT_MISSING_DEPENDENCY: + return "CUFFT_MISSING_DEPENDENCY"; + + case CUFFT_NVRTC_FAILURE: + return "CUFFT_NVRTC_FAILURE"; + + case CUFFT_NVJITLINK_FAILURE: + return "CUFFT_NVJITLINK_FAILURE"; + + case CUFFT_NVSHMEM_FAILURE: + return "CUFFT_NVSHMEM_FAILURE"; + } + + return ""; +} +#endif + +#ifdef CUSPARSEAPI +// cuSPARSE API errors +static const char *_cudaGetErrorEnum(cusparseStatus_t error) { + switch (error) { + case CUSPARSE_STATUS_SUCCESS: + return "CUSPARSE_STATUS_SUCCESS"; + + case CUSPARSE_STATUS_NOT_INITIALIZED: + return "CUSPARSE_STATUS_NOT_INITIALIZED"; + + case CUSPARSE_STATUS_ALLOC_FAILED: + return "CUSPARSE_STATUS_ALLOC_FAILED"; + + case CUSPARSE_STATUS_INVALID_VALUE: + return "CUSPARSE_STATUS_INVALID_VALUE"; + + case CUSPARSE_STATUS_ARCH_MISMATCH: + return "CUSPARSE_STATUS_ARCH_MISMATCH"; + + case CUSPARSE_STATUS_MAPPING_ERROR: + return "CUSPARSE_STATUS_MAPPING_ERROR"; + + case CUSPARSE_STATUS_EXECUTION_FAILED: + return "CUSPARSE_STATUS_EXECUTION_FAILED"; + + case CUSPARSE_STATUS_INTERNAL_ERROR: + return "CUSPARSE_STATUS_INTERNAL_ERROR"; + + case CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + } + + return ""; +} +#endif + +#ifdef CUSOLVER_COMMON_H_ +// cuSOLVER API errors +static const char *_cudaGetErrorEnum(cusolverStatus_t error) { + switch (error) { + case CUSOLVER_STATUS_SUCCESS: + return "CUSOLVER_STATUS_SUCCESS"; + case CUSOLVER_STATUS_NOT_INITIALIZED: + return "CUSOLVER_STATUS_NOT_INITIALIZED"; + case CUSOLVER_STATUS_ALLOC_FAILED: + return "CUSOLVER_STATUS_ALLOC_FAILED"; + case CUSOLVER_STATUS_INVALID_VALUE: + return "CUSOLVER_STATUS_INVALID_VALUE"; + case CUSOLVER_STATUS_ARCH_MISMATCH: + return "CUSOLVER_STATUS_ARCH_MISMATCH"; + case CUSOLVER_STATUS_MAPPING_ERROR: + return "CUSOLVER_STATUS_MAPPING_ERROR"; + case CUSOLVER_STATUS_EXECUTION_FAILED: + return "CUSOLVER_STATUS_EXECUTION_FAILED"; + case CUSOLVER_STATUS_INTERNAL_ERROR: + return "CUSOLVER_STATUS_INTERNAL_ERROR"; + case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + case CUSOLVER_STATUS_NOT_SUPPORTED: + return "CUSOLVER_STATUS_NOT_SUPPORTED "; + case CUSOLVER_STATUS_ZERO_PIVOT: + return "CUSOLVER_STATUS_ZERO_PIVOT"; + case CUSOLVER_STATUS_INVALID_LICENSE: + return "CUSOLVER_STATUS_INVALID_LICENSE"; + } + + return ""; +} +#endif + +#ifdef CURAND_H_ +// cuRAND API errors +static const char *_cudaGetErrorEnum(curandStatus_t error) { + switch (error) { + case CURAND_STATUS_SUCCESS: + return "CURAND_STATUS_SUCCESS"; + + case CURAND_STATUS_VERSION_MISMATCH: + return "CURAND_STATUS_VERSION_MISMATCH"; + + case CURAND_STATUS_NOT_INITIALIZED: + return "CURAND_STATUS_NOT_INITIALIZED"; + + case CURAND_STATUS_ALLOCATION_FAILED: + return "CURAND_STATUS_ALLOCATION_FAILED"; + + case CURAND_STATUS_TYPE_ERROR: + return "CURAND_STATUS_TYPE_ERROR"; + + case CURAND_STATUS_OUT_OF_RANGE: + return "CURAND_STATUS_OUT_OF_RANGE"; + + case CURAND_STATUS_LENGTH_NOT_MULTIPLE: + return "CURAND_STATUS_LENGTH_NOT_MULTIPLE"; + + case CURAND_STATUS_DOUBLE_PRECISION_REQUIRED: + return "CURAND_STATUS_DOUBLE_PRECISION_REQUIRED"; + + case CURAND_STATUS_LAUNCH_FAILURE: + return "CURAND_STATUS_LAUNCH_FAILURE"; + + case CURAND_STATUS_PREEXISTING_FAILURE: + return "CURAND_STATUS_PREEXISTING_FAILURE"; + + case CURAND_STATUS_INITIALIZATION_FAILED: + return "CURAND_STATUS_INITIALIZATION_FAILED"; + + case CURAND_STATUS_ARCH_MISMATCH: + return "CURAND_STATUS_ARCH_MISMATCH"; + + case CURAND_STATUS_INTERNAL_ERROR: + return "CURAND_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NVJPEGAPI +// nvJPEG API errors +static const char *_cudaGetErrorEnum(nvjpegStatus_t error) { + switch (error) { + case NVJPEG_STATUS_SUCCESS: + return "NVJPEG_STATUS_SUCCESS"; + + case NVJPEG_STATUS_NOT_INITIALIZED: + return "NVJPEG_STATUS_NOT_INITIALIZED"; + + case NVJPEG_STATUS_INVALID_PARAMETER: + return "NVJPEG_STATUS_INVALID_PARAMETER"; + + case NVJPEG_STATUS_BAD_JPEG: + return "NVJPEG_STATUS_BAD_JPEG"; + + case NVJPEG_STATUS_JPEG_NOT_SUPPORTED: + return "NVJPEG_STATUS_JPEG_NOT_SUPPORTED"; + + case NVJPEG_STATUS_ALLOCATOR_FAILURE: + return "NVJPEG_STATUS_ALLOCATOR_FAILURE"; + + case NVJPEG_STATUS_EXECUTION_FAILED: + return "NVJPEG_STATUS_EXECUTION_FAILED"; + + case NVJPEG_STATUS_ARCH_MISMATCH: + return "NVJPEG_STATUS_ARCH_MISMATCH"; + + case NVJPEG_STATUS_INTERNAL_ERROR: + return "NVJPEG_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NV_NPPIDEFS_H +// NPP API errors +static const char *_cudaGetErrorEnum(NppStatus error) { + switch (error) { + case NPP_NOT_SUPPORTED_MODE_ERROR: + return "NPP_NOT_SUPPORTED_MODE_ERROR"; + + case NPP_ROUND_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ROUND_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_RESIZE_NO_OPERATION_ERROR: + return "NPP_RESIZE_NO_OPERATION_ERROR"; + + case NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY: + return "NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_BAD_ARG_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFF_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECT_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUAD_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEM_ALLOC_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTO_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_INPUT: + return "NPP_INVALID_INPUT"; + + case NPP_POINTER_ERROR: + return "NPP_POINTER_ERROR"; + + case NPP_WARNING: + return "NPP_WARNING"; + + case NPP_ODD_ROI_WARNING: + return "NPP_ODD_ROI_WARNING"; +#else + + // These are for CUDA 5.5 or higher + case NPP_BAD_ARGUMENT_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFFICIENT_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECTANGLE_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUADRANGLE_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEMORY_ALLOCATION_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_HOST_POINTER_ERROR: + return "NPP_INVALID_HOST_POINTER_ERROR"; + + case NPP_INVALID_DEVICE_POINTER_ERROR: + return "NPP_INVALID_DEVICE_POINTER_ERROR"; +#endif + + case NPP_LUT_NUMBER_OF_LEVELS_ERROR: + return "NPP_LUT_NUMBER_OF_LEVELS_ERROR"; + + case NPP_TEXTURE_BIND_ERROR: + return "NPP_TEXTURE_BIND_ERROR"; + + case NPP_WRONG_INTERSECTION_ROI_ERROR: + return "NPP_WRONG_INTERSECTION_ROI_ERROR"; + + case NPP_NOT_EVEN_STEP_ERROR: + return "NPP_NOT_EVEN_STEP_ERROR"; + + case NPP_INTERPOLATION_ERROR: + return "NPP_INTERPOLATION_ERROR"; + + case NPP_RESIZE_FACTOR_ERROR: + return "NPP_RESIZE_FACTOR_ERROR"; + + case NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR: + return "NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_MEMFREE_ERR: + return "NPP_MEMFREE_ERR"; + + case NPP_MEMSET_ERR: + return "NPP_MEMSET_ERR"; + + case NPP_MEMCPY_ERR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERR: + return "NPP_MIRROR_FLIP_ERR"; +#else + + case NPP_MEMFREE_ERROR: + return "NPP_MEMFREE_ERROR"; + + case NPP_MEMSET_ERROR: + return "NPP_MEMSET_ERROR"; + + case NPP_MEMCPY_ERROR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERROR: + return "NPP_MIRROR_FLIP_ERROR"; +#endif + + case NPP_ALIGNMENT_ERROR: + return "NPP_ALIGNMENT_ERROR"; + + case NPP_STEP_ERROR: + return "NPP_STEP_ERROR"; + + case NPP_SIZE_ERROR: + return "NPP_SIZE_ERROR"; + + case NPP_NULL_POINTER_ERROR: + return "NPP_NULL_POINTER_ERROR"; + + case NPP_CUDA_KERNEL_EXECUTION_ERROR: + return "NPP_CUDA_KERNEL_EXECUTION_ERROR"; + + case NPP_NOT_IMPLEMENTED_ERROR: + return "NPP_NOT_IMPLEMENTED_ERROR"; + + case NPP_ERROR: + return "NPP_ERROR"; + + case NPP_SUCCESS: + return "NPP_SUCCESS"; + + case NPP_WRONG_INTERSECTION_QUAD_WARNING: + return "NPP_WRONG_INTERSECTION_QUAD_WARNING"; + + case NPP_MISALIGNED_DST_ROI_WARNING: + return "NPP_MISALIGNED_DST_ROI_WARNING"; + + case NPP_AFFINE_QUAD_INCORRECT_WARNING: + return "NPP_AFFINE_QUAD_INCORRECT_WARNING"; + + case NPP_DOUBLE_SIZE_WARNING: + return "NPP_DOUBLE_SIZE_WARNING"; + + case NPP_WRONG_INTERSECTION_ROI_WARNING: + return "NPP_WRONG_INTERSECTION_ROI_WARNING"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x6000 + /* These are 6.0 or higher */ + case NPP_LUT_PALETTE_BITSIZE_ERROR: + return "NPP_LUT_PALETTE_BITSIZE_ERROR"; + + case NPP_ZC_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ZC_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_QUALITY_INDEX_ERROR: + return "NPP_QUALITY_INDEX_ERROR"; + + case NPP_CHANNEL_ORDER_ERROR: + return "NPP_CHANNEL_ORDER_ERROR"; + + case NPP_ZERO_MASK_VALUE_ERROR: + return "NPP_ZERO_MASK_VALUE_ERROR"; + + case NPP_NUMBER_OF_CHANNELS_ERROR: + return "NPP_NUMBER_OF_CHANNELS_ERROR"; + + case NPP_COI_ERROR: + return "NPP_COI_ERROR"; + + case NPP_DIVISOR_ERROR: + return "NPP_DIVISOR_ERROR"; + + case NPP_CHANNEL_ERROR: + return "NPP_CHANNEL_ERROR"; + + case NPP_STRIDE_ERROR: + return "NPP_STRIDE_ERROR"; + + case NPP_ANCHOR_ERROR: + return "NPP_ANCHOR_ERROR"; + + case NPP_MASK_SIZE_ERROR: + return "NPP_MASK_SIZE_ERROR"; + + case NPP_MOMENT_00_ZERO_ERROR: + return "NPP_MOMENT_00_ZERO_ERROR"; + + case NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR: + return "NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR"; + + case NPP_THRESHOLD_ERROR: + return "NPP_THRESHOLD_ERROR"; + + case NPP_CONTEXT_MATCH_ERROR: + return "NPP_CONTEXT_MATCH_ERROR"; + + case NPP_FFT_FLAG_ERROR: + return "NPP_FFT_FLAG_ERROR"; + + case NPP_FFT_ORDER_ERROR: + return "NPP_FFT_ORDER_ERROR"; + + case NPP_SCALE_RANGE_ERROR: + return "NPP_SCALE_RANGE_ERROR"; + + case NPP_DATA_TYPE_ERROR: + return "NPP_DATA_TYPE_ERROR"; + + case NPP_OUT_OFF_RANGE_ERROR: + return "NPP_OUT_OFF_RANGE_ERROR"; + + case NPP_DIVIDE_BY_ZERO_ERROR: + return "NPP_DIVIDE_BY_ZERO_ERROR"; + + case NPP_RANGE_ERROR: + return "NPP_RANGE_ERROR"; + + case NPP_NO_MEMORY_ERROR: + return "NPP_NO_MEMORY_ERROR"; + + case NPP_ERROR_RESERVED: + return "NPP_ERROR_RESERVED"; + + case NPP_NO_OPERATION_WARNING: + return "NPP_NO_OPERATION_WARNING"; + + case NPP_DIVIDE_BY_ZERO_WARNING: + return "NPP_DIVIDE_BY_ZERO_WARNING"; +#endif + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x7000 + /* These are 7.0 or higher */ + case NPP_OVERFLOW_ERROR: + return "NPP_OVERFLOW_ERROR"; + + case NPP_CORRUPTED_DATA_ERROR: + return "NPP_CORRUPTED_DATA_ERROR"; +#endif + } + + return ""; +} +#endif + +template +void check(T result, char const *const func, const char *const file, + int const line) { + if (result) { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \"%s\" \n", file, line, + static_cast(result), _cudaGetErrorEnum(result), func); + exit(EXIT_FAILURE); + } +} + +#ifdef __DRIVER_TYPES_H__ +// This will output the proper CUDA error strings in the event +// that a CUDA host call returns an error +#define checkCudaErrors(val) check((val), #val, __FILE__, __LINE__) + +// This will output the proper error string when calling cudaGetLastError +#define getLastCudaError(msg) __getLastCudaError(msg, __FILE__, __LINE__) + +inline void __getLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + exit(EXIT_FAILURE); + } +} + +// This will only print the proper error string when calling cudaGetLastError +// but not exit program incase error detected. +#define printLastCudaError(msg) __printLastCudaError(msg, __FILE__, __LINE__) + +inline void __printLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + } +} +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif + +// Float To Int conversion +inline int ftoi(float value) { + return (value >= 0 ? static_cast(value + 0.5) + : static_cast(value - 0.5)); +} + +// Beginning of GPU Architecture definitions +inline int _ConvertSMVer2Cores(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the # of cores per SM + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + int Cores; + } sSMtoCores; + + sSMtoCores nGpuArchCoresPerSM[] = { + {0x30, 192}, + {0x32, 192}, + {0x35, 192}, + {0x37, 192}, + {0x50, 128}, + {0x52, 128}, + {0x53, 128}, + {0x60, 64}, + {0x61, 128}, + {0x62, 128}, + {0x70, 64}, + {0x72, 64}, + {0x75, 64}, + {0x80, 64}, + {0x86, 128}, + {0x87, 128}, + {0x89, 128}, + {0x90, 128}, + {0xa0, 128}, + {0xa1, 128}, + {0xa3, 128}, + {0xb0, 128}, + {0xc0, 128}, + {0xc1, 128}, + {-1, -1}}; + + int index = 0; + + while (nGpuArchCoresPerSM[index].SM != -1) { + if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)) { + return nGpuArchCoresPerSM[index].Cores; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoCores for SM %d.%d is undefined." + " Default to use %d Cores/SM\n", + major, minor, nGpuArchCoresPerSM[index - 1].Cores); + return nGpuArchCoresPerSM[index - 1].Cores; +} + +inline const char* _ConvertSMVer2ArchName(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the GPU Arch name) + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + const char* name; + } sSMtoArchName; + + sSMtoArchName nGpuArchNameSM[] = { + {0x30, "Kepler"}, + {0x32, "Kepler"}, + {0x35, "Kepler"}, + {0x37, "Kepler"}, + {0x50, "Maxwell"}, + {0x52, "Maxwell"}, + {0x53, "Maxwell"}, + {0x60, "Pascal"}, + {0x61, "Pascal"}, + {0x62, "Pascal"}, + {0x70, "Volta"}, + {0x72, "Xavier"}, + {0x75, "Turing"}, + {0x80, "Ampere"}, + {0x86, "Ampere"}, + {0x87, "Ampere"}, + {0x89, "Ada"}, + {0x90, "Hopper"}, + {0xa0, "Blackwell"}, + {0xa1, "Blackwell"}, + {0xa3, "Blackwell"}, + {0xb0, "Blackwell"}, + {0xc0, "Blackwell"}, + {0xc1, "Blackwell"}, + {-1, "Graphics Device"}}; + + int index = 0; + + while (nGpuArchNameSM[index].SM != -1) { + if (nGpuArchNameSM[index].SM == ((major << 4) + minor)) { + return nGpuArchNameSM[index].name; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoArchName for SM %d.%d is undefined." + " Default to use %s\n", + major, minor, nGpuArchNameSM[index - 1].name); + return nGpuArchNameSM[index - 1].name; +} + // end of GPU Architecture definitions + +#ifdef __CUDA_RUNTIME_H__ +// General GPU Device CUDA Initialization +inline int gpuDeviceInit(int devID) { + int device_count; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuDeviceInit() CUDA error: " + "no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + if (devID < 0) { + devID = 0; + } + + if (devID > device_count - 1) { + fprintf(stderr, "\n"); + fprintf(stderr, ">> %d CUDA capable GPU device(s) detected. <<\n", + device_count); + fprintf(stderr, + ">> gpuDeviceInit (-device=%d) is not a valid" + " GPU device. <<\n", + devID); + fprintf(stderr, "\n"); + return -devID; + } + + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + if (computeMode == cudaComputeModeProhibited) { + fprintf(stderr, + "Error: device is running in , no threads can use cudaSetDevice().\n"); + return -1; + } + + if (major < 1) { + fprintf(stderr, "gpuDeviceInit(): GPU device does not support CUDA.\n"); + exit(EXIT_FAILURE); + } + + checkCudaErrors(cudaSetDevice(devID)); + printf("gpuDeviceInit() CUDA Device [%d]: \"%s\n", devID, _ConvertSMVer2ArchName(major, minor)); + + return devID; +} + +// This function returns the best GPU (with maximum GFLOPS) +inline int gpuGetMaxGflopsDeviceId() { + int current_device = 0, sm_per_multiproc = 0; + int max_perf_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + uint64_t max_compute_perf = 0; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the best CUDA capable GPU device + current_device = 0; + + while (current_device < device_count) { + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + + // If this GPU is not running on Compute Mode prohibited, + // then we can add it to the list + if (computeMode != cudaComputeModeProhibited) { + if (major == 9999 && minor == 9999) { + sm_per_multiproc = 1; + } else { + sm_per_multiproc = + _ConvertSMVer2Cores(major, minor); + } + int multiProcessorCount = 0, clockRate = 0; + checkCudaErrors(cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, current_device)); + cudaError_t result = cudaDeviceGetAttribute(&clockRate, cudaDevAttrClockRate, current_device); + if (result != cudaSuccess) { + // If cudaDevAttrClockRate attribute is not supported we + // set clockRate as 1, to consider GPU with most SMs and CUDA Cores. + if(result == cudaErrorInvalidValue) { + clockRate = 1; + } + else { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \n", __FILE__, __LINE__, + static_cast(result), _cudaGetErrorEnum(result)); + exit(EXIT_FAILURE); + } + } + uint64_t compute_perf = (uint64_t)multiProcessorCount * sm_per_multiproc * clockRate; + + if (compute_perf > max_compute_perf) { + max_compute_perf = compute_perf; + max_perf_device = current_device; + } + } else { + devices_prohibited++; + } + + ++current_device; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " all devices have compute mode prohibited.\n"); + exit(EXIT_FAILURE); + } + + return max_perf_device; +} + +// Initialization code to find the best CUDA Device +inline int findCudaDevice(int argc, const char **argv) { + int devID = 0; + + // If the command-line has a device number specified, use it + if (checkCmdLineFlag(argc, argv, "device")) { + devID = getCmdLineArgumentInt(argc, argv, "device="); + + if (devID < 0) { + printf("Invalid command line parameter\n "); + exit(EXIT_FAILURE); + } else { + devID = gpuDeviceInit(devID); + + if (devID < 0) { + printf("exiting...\n"); + exit(EXIT_FAILURE); + } + } + } else { + // Otherwise pick the device with highest Gflops/s + devID = gpuGetMaxGflopsDeviceId(); + checkCudaErrors(cudaSetDevice(devID)); + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + devID, _ConvertSMVer2ArchName(major, minor), major, minor); + + } + + return devID; +} + +inline int findIntegratedGPU() { + int current_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, "CUDA error: no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the integrated GPU which is compute capable + while (current_device < device_count) { + int computeMode = -1, integrated = -1; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&integrated, cudaDevAttrIntegrated, current_device)); + // If GPU is integrated and is not running on Compute Mode prohibited, + // then cuda can map to GLES resource + if (integrated && (computeMode != cudaComputeModeProhibited)) { + checkCudaErrors(cudaSetDevice(current_device)); + + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + current_device, _ConvertSMVer2ArchName(major, minor), major, minor); + + return current_device; + } else { + devices_prohibited++; + } + + current_device++; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "CUDA error:" + " No GLES-CUDA Interop capable GPU found.\n"); + exit(EXIT_FAILURE); + } + + return -1; +} + +// General check for CUDA GPU SM Capabilities +inline bool checkCudaCapabilities(int major_version, int minor_version) { + int dev; + int major = 0, minor = 0; + + checkCudaErrors(cudaGetDevice(&dev)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, dev)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, dev)); + + if ((major > major_version) || + (major == major_version && + minor >= minor_version)) { + printf(" Device %d: <%16s >, Compute SM %d.%d detected\n", dev, + _ConvertSMVer2ArchName(major, minor), major, minor); + return true; + } else { + printf( + " No GPU device was found that can support " + "CUDA compute capability %d.%d.\n", + major_version, minor_version); + return false; + } +} +#endif + + // end of CUDA Helper Functions + +#endif // COMMON_HELPER_CUDA_H_ diff --git a/src/cuda/H100/image/FDTD3d/helper_functions.h b/src/cuda/H100/image/FDTD3d/helper_functions.h new file mode 100644 index 000000000..f71da5761 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/helper_functions.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, +// timers, image helpers, etc) +#ifndef COMMON_HELPER_FUNCTIONS_H_ +#define COMMON_HELPER_FUNCTIONS_H_ + +#ifdef WIN32 +#pragma warning(disable : 4996) +#endif + +// includes, project +#include +#include "exception.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +// includes, timer, string parsing, image helpers +#include "helper_image.h" // helper functions for image compare, dump, data comparisons +#include "helper_string.h" // helper functions for string parsing +#include "helper_timer.h" // helper functions for timers + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#endif // COMMON_HELPER_FUNCTIONS_H_ diff --git a/src/cuda/H100/image/FDTD3d/helper_image.h b/src/cuda/H100/image/FDTD3d/helper_image.h new file mode 100644 index 000000000..7e81b0cd8 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/helper_image.h @@ -0,0 +1,1001 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (image,bitmap) +#ifndef COMMON_HELPER_IMAGE_H_ +#define COMMON_HELPER_IMAGE_H_ + +#include +#include "exception.h" +#include +#include + +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a < b) ? a : b) +#endif +#ifndef MAX +#define MAX(a, b) ((a > b) ? a : b) +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#include "helper_string.h" + +// namespace unnamed (internal) +namespace helper_image_internal { +//! size of PGM file header +const unsigned int PGMHeaderSize = 0x40; + +// types + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterFromUByte; + +//! Data converter from unsigned char / unsigned byte +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val); + } +}; + +//! Data converter from unsigned char / unsigned byte to float +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val) / 255.0f; + } +}; + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterToUByte; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator (essentially a passthru + //! @return converted value + //! @param val value to convert + unsigned char operator()(const unsigned char &val) { return val; } +}; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + unsigned char operator()(const float &val) { + return static_cast(val * 255.0f); + } +}; +} // namespace helper_image_internal + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#else +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#endif + +inline bool __loadPPM(const char *file, unsigned char **data, unsigned int *w, + unsigned int *h, unsigned int *channels) { + FILE *fp = NULL; + + if (FOPEN_FAIL(FOPEN(fp, file, "rb"))) { + std::cerr << "__LoadPPM() : Failed to open file: " << file << std::endl; + return false; + } + + // check header + char header[helper_image_internal::PGMHeaderSize]; + + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" << std::endl; + return false; + } + + if (strncmp(header, "P5", 2) == 0) { + *channels = 1; + } else if (strncmp(header, "P6", 2) == 0) { + *channels = 3; + } else { + std::cerr << "__LoadPPM() : File is not a PPM or PGM image" << std::endl; + *channels = 0; + return false; + } + + // parse header, read maxval, width and height + unsigned int width = 0; + unsigned int height = 0; + unsigned int maxval = 0; + unsigned int i = 0; + + while (i < 3) { + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" + << std::endl; + return false; + } + + if (header[0] == '#') { + continue; + } + + if (i == 0) { + i += SSCANF(header, "%u %u %u", &width, &height, &maxval); + } else if (i == 1) { + i += SSCANF(header, "%u %u", &height, &maxval); + } else if (i == 2) { + i += SSCANF(header, "%u", &maxval); + } + } + + // check if given handle for the data is initialized + if (NULL != *data) { + if (*w != width || *h != height) { + std::cerr << "__LoadPPM() : Invalid image dimensions." << std::endl; + } + } else { + *data = (unsigned char *)malloc(sizeof(unsigned char) * width * height * + *channels); + *w = width; + *h = height; + } + + // read and close file + if (fread(*data, sizeof(unsigned char), width * height * *channels, fp) == + 0) { + std::cerr << "__LoadPPM() read data returned error." << std::endl; + } + + fclose(fp); + + return true; +} + +template +inline bool sdkLoadPGM(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = NULL; + unsigned int channels; + + if (true != __loadPPM(file, &idata, w, h, &channels)) { + return false; + } + + unsigned int size = *w * *h * channels; + + // initialize mem if necessary + // the correct size is checked / set in loadPGMc() + if (NULL == *data) { + *data = reinterpret_cast(malloc(sizeof(T) * size)); + } + + // copy and cast data + std::transform(idata, idata + size, *data, + helper_image_internal::ConverterFromUByte()); + + free(idata); + + return true; +} + +template +inline bool sdkLoadPPM4(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = reinterpret_cast(malloc(sizeof(T) * size * 4)); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool __savePPM(const char *file, unsigned char *data, unsigned int w, + unsigned int h, unsigned int channels) { + assert(NULL != data); + assert(w > 0); + assert(h > 0); + + std::fstream fh(file, std::fstream::out | std::fstream::binary); + + if (fh.bad()) { + std::cerr << "__savePPM() : Opening file failed." << std::endl; + return false; + } + + if (channels == 1) { + fh << "P5\n"; + } else if (channels == 3) { + fh << "P6\n"; + } else { + std::cerr << "__savePPM() : Invalid number of channels." << std::endl; + return false; + } + + fh << w << "\n" << h << "\n" << 0xff << std::endl; + + for (unsigned int i = 0; (i < (w * h * channels)) && fh.good(); ++i) { + fh << data[i]; + } + + fh.flush(); + + if (fh.bad()) { + std::cerr << "__savePPM() : Writing data failed." << std::endl; + return false; + } + + fh.close(); + + return true; +} + +template +inline bool sdkSavePGM(const char *file, T *data, unsigned int w, + unsigned int h) { + unsigned int size = w * h; + unsigned char *idata = (unsigned char *)malloc(sizeof(unsigned char) * size); + + std::transform(data, data + size, idata, + helper_image_internal::ConverterToUByte()); + + // write file + bool result = __savePPM(file, idata, w, h, 1); + + // cleanup + free(idata); + + return result; +} + +inline bool sdkSavePPM4ub(const char *file, unsigned char *data, unsigned int w, + unsigned int h) { + // strip 4th component + int size = w * h; + unsigned char *ndata = + (unsigned char *)malloc(sizeof(unsigned char) * size * 3); + unsigned char *ptr = ndata; + + for (int i = 0; i < size; i++) { + *ptr++ = *data++; + *ptr++ = *data++; + *ptr++ = *data++; + data++; + } + + bool result = __savePPM(file, ndata, w, h, 3); + free(ndata); + return result; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFile(const char *filename, T **data, unsigned int *len, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // intermediate storage for the data read + std::vector data_read; + + // open file for reading + FILE *fh = NULL; + + // check if filestream is valid + if (FOPEN_FAIL(FOPEN(fh, filename, "r"))) { + printf("Unable to open input file: %s\n", filename); + return false; + } + + // read all data elements + T token; + + while (!feof(fh)) { + fscanf(fh, "%f", &token); + data_read.push_back(token); + } + + // the last element is read twice + data_read.pop_back(); + fclose(fh); + + // check if the given handle is already initialized + if (NULL != *data) { + if (*len != data_read.size()) { + std::cerr << "sdkReadFile() : Initialized memory given but " + << "size mismatch with signal read " + << "(data read / data init = " << (unsigned int)data_read.size() + << " / " << *len << ")" << std::endl; + + return false; + } + } else { + // allocate storage for the data read + *data = reinterpret_cast(malloc(sizeof(T) * data_read.size())); + // store signal size + *len = static_cast(data_read.size()); + } + + // copy data + memcpy(*data, &data_read.front(), sizeof(T) * data_read.size()); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFileBlocks(const char *filename, T **data, unsigned int *len, + unsigned int block_num, unsigned int block_size, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // open file for reading + FILE *fh = fopen(filename, "rb"); + + if (fh == NULL && verbose) { + std::cerr << "sdkReadFile() : Opening file failed." << std::endl; + return false; + } + + // check if the given handle is already initialized + // allocate storage for the data read + data[block_num] = reinterpret_cast(malloc(block_size)); + + // read all data elements + fseek(fh, block_num * block_size, SEEK_SET); + *len = fread(data[block_num], sizeof(T), block_size / sizeof(T), fh); + + fclose(fh); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Write a data file \filename +//! @return true if writing the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data data to write +//! @param len number of data elements in data, -1 on error +//! @param epsilon epsilon for comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkWriteFile(const char *filename, const T *data, unsigned int len, + const S epsilon, bool verbose, bool append = false) { + assert(NULL != filename); + assert(NULL != data); + + // open file for writing + // if (append) { + std::fstream fh(filename, std::fstream::out | std::fstream::ate); + + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename + << " for write/append." << std::endl; + } + + /* } else { + std::fstream fh(filename, std::fstream::out); + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename << " for + write." << std::endl; + } + } + */ + + // check if filestream is valid + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Opening file failed." << std::endl; + } + + return false; + } + + // first write epsilon + fh << "# " << epsilon << "\n"; + + // write data + for (unsigned int i = 0; (i < len) && (fh.good()); ++i) { + fh << data[i] << ' '; + } + + // Check if writing succeeded + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Writing file failed." << std::endl; + } + + return false; + } + + // file ends with nl + fh << std::endl; + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference timer_interface to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareData(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + bool result = true; + unsigned int error_count = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = static_cast(reference[i]) - static_cast(data[i]); + bool comp = (diff <= epsilon) && (diff >= -epsilon); + result &= comp; + + error_count += !comp; + +#if 0 + + if (!comp) { + std::cerr << "ERROR, i = " << i << ",\t " + << reference[i] << " / " + << data[i] + << " (reference / data)\n"; + } + +#endif + } + + if (threshold == 0.0f) { + return (result) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return (len * threshold > error_count) ? true : false; + } +} + +#ifndef __MIN_EPSILON_ERROR +#define __MIN_EPSILON_ERROR 1e-3f +#endif + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference handle to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +//! @param epsilon threshold % of (# of bytes) for pass/fail +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareDataAsFloatThreshold(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + // If we set epsilon to be 0, let's set a minimum threshold + float max_error = MAX((float)epsilon, __MIN_EPSILON_ERROR); + int error_count = 0; + bool result = true; + + for (unsigned int i = 0; i < len; ++i) { + float diff = + fabs(static_cast(reference[i]) - static_cast(data[i])); + bool comp = (diff < max_error); + result &= comp; + + if (!comp) { + error_count++; + } + } + + if (threshold == 0.0f) { + if (error_count) { + printf("total # of errors = %d\n", error_count); + } + + return (error_count == 0) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return ((len * threshold > error_count) ? true : false); + } +} + +inline void sdkDumpBin(void *data, unsigned int bytes, const char *filename) { + printf("sdkDumpBin: <%s>\n", filename); + FILE *fp; + FOPEN(fp, filename, "wb"); + fwrite(data, bytes, 1, fp); + fflush(fp); + fclose(fp); +} + +inline bool sdkCompareBin2BinUint(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + unsigned int *src_buffer, *ref_buffer; + FILE *src_fp = NULL, *ref_fp = NULL; + + uint64_t error_count = 0; + size_t fsize = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", + src_file); + error_count++; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", + ref_file, exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + ref_file); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf( + "compareBin2Bin " + " unable to open ref_file: %s\n", + ref_file_path); + error_count++; + } + + if (src_fp && ref_fp) { + src_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + ref_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + + fsize = fread(src_buffer, nelements, sizeof(unsigned int), src_fp); + fsize = fread(ref_buffer, nelements, sizeof(unsigned int), ref_fp); + + printf( + "> compareBin2Bin nelements=%d," + " epsilon=%4.2f, threshold=%4.2f\n", + nelements, epsilon, threshold); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize)); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize)); + + if (!compareData(ref_buffer, src_buffer, nelements, + epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareBin2BinFloat(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + float *src_buffer = NULL, *ref_buffer = NULL; + FILE *src_fp = NULL, *ref_fp = NULL; + size_t fsize = 0; + + uint64_t error_count = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", src_file); + error_count = 1; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", ref_file, + exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + exec_path); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf("compareBin2Bin unable to open ref_file: %s\n", + ref_file_path); + error_count = 1; + } + + if (src_fp && ref_fp) { + src_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + ref_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + + printf( + "> compareBin2Bin nelements=%d, epsilon=%4.2f," + " threshold=%4.2f\n", + nelements, epsilon, threshold); + fsize = fread(src_buffer, sizeof(float), nelements, src_fp); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize * sizeof(float))); + fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize * sizeof(float))); + + if (!compareDataAsFloatThreshold( + ref_buffer, src_buffer, nelements, epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareL2fe(const float *reference, const float *data, + const unsigned int len, const float epsilon) { + assert(epsilon >= 0); + + float error = 0; + float ref = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = reference[i] - data[i]; + error += diff * diff; + ref += reference[i] * reference[i]; + } + + float normRef = sqrtf(ref); + + if (fabs(ref) < 1e-7) { +#ifdef _DEBUG + std::cerr << "ERROR, reference l2-norm is 0\n"; +#endif + return false; + } + + float normError = sqrtf(error); + error = normError / normRef; + bool result = error < epsilon; +#ifdef _DEBUG + + if (!result) { + std::cerr << "ERROR, l2-norm error " << error << " is greater than epsilon " + << epsilon << "\n"; + } + +#endif + + return result; +} + +inline bool sdkLoadPPMub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned int channels; + return __loadPPM(file, data, w, h, &channels); +} + +inline bool sdkLoadPPM4ub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = (unsigned char *)malloc(sizeof(unsigned char) * size * 4); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool sdkComparePPM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data, *ref_data; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPM4ub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPM4ub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PPMvsPPM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) { + std::cerr << "PPMvsPPM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + } + + if (compareData(ref_data, src_data, src_width * src_height * 4, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +inline bool sdkComparePGM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data = 0, *ref_data = 0; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPMub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPMub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PGMvsPGM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) + std::cerr << "PGMvsPGM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + + if (compareData(ref_data, src_data, src_width * src_height, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +#endif // COMMON_HELPER_IMAGE_H_ diff --git a/src/cuda/H100/image/FDTD3d/helper_string.h b/src/cuda/H100/image/FDTD3d/helper_string.h new file mode 100644 index 000000000..39a1b3805 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/helper_string.h @@ -0,0 +1,428 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, timers, etc) +#ifndef COMMON_HELPER_STRING_H_ +#define COMMON_HELPER_STRING_H_ + +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#ifndef STRCASECMP +#define STRCASECMP _stricmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP _strnicmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy_s(sFilePath, nLength, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#ifndef SPRINTF +#define SPRINTF sprintf_s +#endif +#else // Linux Includes +#include +#include + +#ifndef STRCASECMP +#define STRCASECMP strcasecmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP strncasecmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy(sFilePath, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#ifndef SPRINTF +#define SPRINTF sprintf +#endif +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// CUDA Utility Helper Functions +inline int stringRemoveDelimiter(char delimiter, const char *string) { + int string_start = 0; + + while (string[string_start] == delimiter) { + string_start++; + } + + if (string_start >= static_cast(strlen(string) - 1)) { + return 0; + } + + return string_start; +} + +inline int getFileExtension(char *filename, char **extension) { + int string_length = static_cast(strlen(filename)); + + while (filename[string_length--] != '.') { + if (string_length == 0) break; + } + + if (string_length > 0) string_length += 2; + + if (string_length == 0) + *extension = NULL; + else + *extension = &filename[string_length]; + + return string_length; +} + +inline bool checkCmdLineFlag(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + + const char *equal_pos = strchr(string_argv, '='); + int argv_length = static_cast( + equal_pos == 0 ? strlen(string_argv) : equal_pos - string_argv); + + int length = static_cast(strlen(string_ref)); + + if (length == argv_length && + !STRNCASECMP(string_argv, string_ref, length)) { + bFound = true; + continue; + } + } + } + + return bFound; +} + +// This function wraps the CUDA Driver API into a template function +template +inline bool getCmdLineArgumentValue(const int argc, const char **argv, + const char *string_ref, T *value) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + *value = (T)atoi(&string_argv[length + auto_inc]); + } + + bFound = true; + i = argc; + } + } + } + + return bFound; +} + +inline int getCmdLineArgumentInt(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + int value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = atoi(&string_argv[length + auto_inc]); + } else { + value = 0; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline float getCmdLineArgumentFloat(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + float value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = static_cast(atof(&string_argv[length + auto_inc])); + } else { + value = 0.f; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline bool getCmdLineArgumentString(const int argc, const char **argv, + const char *string_ref, + char **string_retval) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + char *string_argv = const_cast(&argv[i][string_start]); + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + *string_retval = &string_argv[length + 1]; + bFound = true; + continue; + } + } + } + + if (!bFound) { + *string_retval = NULL; + } + + return bFound; +} + +////////////////////////////////////////////////////////////////////////////// +//! Find the path for a file assuming that +//! files are found in the searchPath. +//! +//! @return the path if succeeded, otherwise 0 +//! @param filename name of the file +//! @param executable_path optional absolute path of the executable +////////////////////////////////////////////////////////////////////////////// +inline char *sdkFindFilePath(const char *filename, + const char *executable_path) { + // defines a variable that is replaced with the name of the + // executable + + // Typical relative search paths to locate needed companion files (e.g. sample + // input data, or JIT source files) The origin for the relative search may be + // the .exe file, a .bat file launching an .exe, a browser .exe launching the + // .exe or .bat, etc + const char *searchPath[] = { + "./", // same dir + "./data/", // same dir + + "../../../../Samples//", // up 4 in tree + "../../../Samples//", // up 3 in tree + "../../Samples//", // up 2 in tree + + "../../../../Samples//data/", // up 4 in tree + "../../../Samples//data/", // up 3 in tree + "../../Samples//data/", // up 2 in tree + + "../../../../Samples/0_Introduction//", // up 4 in tree + "../../../Samples/0_Introduction//", // up 3 in tree + "../../Samples/0_Introduction//", // up 2 in tree + + "../../../../Samples/1_Utilities//", // up 4 in tree + "../../../Samples/1_Utilities//", // up 3 in tree + "../../Samples/1_Utilities//", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//", // up 4 in tree + "../../../Samples/3_CUDA_Features//", // up 3 in tree + "../../Samples/3_CUDA_Features//", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//", // up 3 in tree + "../../Samples/4_CUDA_Libraries//", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//", // up 4 in tree + "../../../Samples/5_Domain_Specific//", // up 3 in tree + "../../Samples/5_Domain_Specific//", // up 2 in tree + + "../../../../Samples/6_Performance//", // up 4 in tree + "../../../Samples/6_Performance//", // up 3 in tree + "../../Samples/6_Performance//", // up 2 in tree + + "../../../../Samples/0_Introduction//data/", // up 4 in tree + "../../../Samples/0_Introduction//data/", // up 3 in tree + "../../Samples/0_Introduction//data/", // up 2 in tree + + "../../../../Samples/1_Utilities//data/", // up 4 in tree + "../../../Samples/1_Utilities//data/", // up 3 in tree + "../../Samples/1_Utilities//data/", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//data/", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//data/", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//data/", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//data/", // up 4 in tree + "../../../Samples/3_CUDA_Features//data/", // up 3 in tree + "../../Samples/3_CUDA_Features//data/", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//data/", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//data/", // up 3 in tree + "../../Samples/4_CUDA_Libraries//data/", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//data/", // up 4 in tree + "../../../Samples/5_Domain_Specific//data/", // up 3 in tree + "../../Samples/5_Domain_Specific//data/", // up 2 in tree + + "../../../../Samples/6_Performance//data/", // up 4 in tree + "../../../Samples/6_Performance//data/", // up 3 in tree + "../../Samples/6_Performance//data/", // up 2 in tree + + "../../../../Common/data/", // up 4 in tree + "../../../Common/data/", // up 3 in tree + "../../Common/data/" // up 2 in tree + }; + + // Extract the executable name + std::string executable_name; + + if (executable_path != 0) { + executable_name = std::string(executable_path); + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + // Windows path delimiter + size_t delimiter_pos = executable_name.find_last_of('\\'); + executable_name.erase(0, delimiter_pos + 1); + + if (executable_name.rfind(".exe") != std::string::npos) { + // we strip .exe, only if the .exe is found + executable_name.resize(executable_name.size() - 4); + } + +#else + // Linux & OSX path delimiter + size_t delimiter_pos = executable_name.find_last_of('/'); + executable_name.erase(0, delimiter_pos + 1); +#endif + } + + // Loop over all search paths and return the first hit + for (unsigned int i = 0; i < sizeof(searchPath) / sizeof(char *); ++i) { + std::string path(searchPath[i]); + size_t executable_name_pos = path.find(""); + + // If there is executable_name variable in the searchPath + // replace it with the value + if (executable_name_pos != std::string::npos) { + if (executable_path != 0) { + path.replace(executable_name_pos, strlen(""), + executable_name); + } else { + // Skip this path entry if no executable argument is given + continue; + } + } + +#ifdef _DEBUG + printf("sdkFindFilePath <%s> in %s\n", filename, path.c_str()); +#endif + + // Test if the file exists + path.append(filename); + FILE *fp; + FOPEN(fp, path.c_str(), "rb"); + + if (fp != NULL) { + fclose(fp); + // File found + // returning an allocated array here for backwards compatibility reasons + char *file_path = reinterpret_cast(malloc(path.length() + 1)); + STRCPY(file_path, path.length() + 1, path.c_str()); + return file_path; + } + + if (fp) { + fclose(fp); + } + } + + // File not found + printf("\nerror: sdkFindFilePath: file <%s> not found!\n", filename); + return 0; +} + +#endif // COMMON_HELPER_STRING_H_ diff --git a/src/cuda/H100/image/FDTD3d/helper_timer.h b/src/cuda/H100/image/FDTD3d/helper_timer.h new file mode 100644 index 000000000..0614a7802 --- /dev/null +++ b/src/cuda/H100/image/FDTD3d/helper_timer.h @@ -0,0 +1,465 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Helper Timing Functions +#ifndef COMMON_HELPER_TIMER_H_ +#define COMMON_HELPER_TIMER_H_ + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// includes, system +#include + +// includes, project +#include "exception.h" + +// Definition of the StopWatch Interface, this is used if we don't want to use +// the CUT functions But rather in a self contained class interface +class StopWatchInterface { + public: + StopWatchInterface() {} + virtual ~StopWatchInterface() {} + + public: + //! Start time measurement + virtual void start() = 0; + + //! Stop time measurement + virtual void stop() = 0; + + //! Reset time counters to zero + virtual void reset() = 0; + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + virtual float getTime() = 0; + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + virtual float getAverageTime() = 0; +}; + +////////////////////////////////////////////////////////////////// +// Begin Stopwatch timer class definitions for all OS platforms // +////////////////////////////////////////////////////////////////// +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +// includes, system +#define WINDOWS_LEAN_AND_MEAN +#include +#undef min +#undef max + +//! Windows specific implementation of StopWatch +class StopWatchWin : public StopWatchInterface { + public: + //! Constructor, default + StopWatchWin() + : start_time(), + end_time(), + diff_time(0.0f), + total_time(0.0f), + running(false), + clock_sessions(0), + freq(0), + freq_set(false) { + if (!freq_set) { + // helper variable + LARGE_INTEGER temp; + + // get the tick frequency from the OS + QueryPerformanceFrequency(reinterpret_cast(&temp)); + + // convert to type in which it is needed + freq = (static_cast(temp.QuadPart)) / 1000.0; + + // rememeber query + freq_set = true; + } + } + + // Destructor + ~StopWatchWin() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // member variables + + //! Start of measurement + LARGE_INTEGER start_time; + //! End of measurement + LARGE_INTEGER end_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; + + //! tick frequency + double freq; + + //! flag if the frequency has been set + bool freq_set; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::start() { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::stop() { + QueryPerformanceCounter(reinterpret_cast(&end_time)); + diff_time = static_cast(((static_cast(end_time.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + + total_time += diff_time; + clock_sessions++; + running = false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + LARGE_INTEGER temp; + QueryPerformanceCounter(reinterpret_cast(&temp)); + retval += static_cast(((static_cast(temp.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +#else +// Declarations for Stopwatch on Linux and Mac OSX +// includes, system +#include +#include + +//! Windows specific implementation of StopWatch +class StopWatchLinux : public StopWatchInterface { + public: + //! Constructor, default + StopWatchLinux() + : start_time(), + diff_time(0.0), + total_time(0.0), + running(false), + clock_sessions(0) {} + + // Destructor + virtual ~StopWatchLinux() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // helper functions + + //! Get difference between start time and current time + inline float getDiffTime(); + + private: + // member variables + + //! Start of measurement + struct timeval start_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::start() { + gettimeofday(&start_time, 0); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::stop() { + diff_time = getDiffTime(); + total_time += diff_time; + running = false; + clock_sessions++; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + gettimeofday(&start_time, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + retval += getDiffTime(); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getDiffTime() { + struct timeval t_time; + gettimeofday(&t_time, 0); + + // time difference in milli-seconds + return static_cast(1000.0 * (t_time.tv_sec - start_time.tv_sec) + + (0.001 * (t_time.tv_usec - start_time.tv_usec))); +} +#endif // WIN32 + +//////////////////////////////////////////////////////////////////////////////// +//! Timer functionality exported + +//////////////////////////////////////////////////////////////////////////////// +//! Create a new timer +//! @return true if a time has been created, otherwise false +//! @param name of the new timer, 0 if the creation failed +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkCreateTimer(StopWatchInterface **timer_interface) { +// printf("sdkCreateTimer called object %08x\n", (void *)*timer_interface); +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + *timer_interface = reinterpret_cast(new StopWatchWin()); +#else + *timer_interface = + reinterpret_cast(new StopWatchLinux()); +#endif + return (*timer_interface != NULL) ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Delete a timer +//! @return true if a time has been deleted, otherwise false +//! @param name of the timer to delete +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkDeleteTimer(StopWatchInterface **timer_interface) { + // printf("sdkDeleteTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + delete *timer_interface; + *timer_interface = NULL; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Start the time with name \a name +//! @param name name of the timer to start +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStartTimer(StopWatchInterface **timer_interface) { + // printf("sdkStartTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->start(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop the time with name \a name. Does not reset. +//! @param name name of the timer to stop +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStopTimer(StopWatchInterface **timer_interface) { + // printf("sdkStopTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->stop(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Resets the timer's counter. +//! @param name name of the timer to reset. +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkResetTimer(StopWatchInterface **timer_interface) { + // printf("sdkResetTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->reset(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Return the average time for timer execution as the total time +//! for the timer dividied by the number of completed (stopped) runs the timer +//! has made. +//! Excludes the current running time if the timer is currently running. +//! @param name name of the timer to return the time of +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetAverageTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetAverageTimerValue called object %08x\n", (void + // *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getAverageTime(); + } else { + return 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Total execution time for the timer over all runs since the last reset +//! or timer creation. +//! @param name name of the timer to obtain the value of. +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetTimerValue called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getTime(); + } else { + return 0.0f; + } +} + +#endif // COMMON_HELPER_TIMER_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/Makefile b/src/cuda/H100/image/dwtHaar1D/Makefile new file mode 100644 index 000000000..b88e7f58c --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/Makefile @@ -0,0 +1,8 @@ +EXECUTABLE := dwtHaar1D +CUFILES := dwtHaar1D.cu +CCFILES := +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +include ../../../common/common.mk diff --git a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu new file mode 100644 index 000000000..f9a4f6029 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu @@ -0,0 +1,386 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* +* 1D DWT for Haar wavelet and signals with a length which is a power of 2. +* The code reduces bank conflicts and non-coalesced reads / writes as +* appropriate but does not fully remove them because the computational +* overhead to achieve this would outweighs the benefit (see inline comments +* for more details). +* Large signals are subdivided into sub-signals with 512 elements and the +* wavelet transform for these is computed with one block over 10 decomposition +* levels. The resulting signal consisting of the approximation coefficients at +* level X is then processed in a subsequent step on the device. This requires +* interblock synchronization which is only possible on host side. +* Detail coefficients which have been computed are not further referenced +* during the decomposition so that they can be stored directly in their final +* position in global memory. The transform and its storing scheme preserve +* locality in the coefficients so that these writes are coalesced. +* Approximation coefficients are stored in shared memory because they are +* needed to compute the subsequent decomposition step. The top most +* approximation coefficient for a sub-signal processed by one block is stored +* in a special global memory location to simplify the processing after the +* interblock synchronization. +* Most books on wavelets explain the Haar wavelet decomposition. A good freely +* available resource is the Wavelet primer by Stollnitz et al. +* http://grail.cs.washington.edu/projects/wavelets/article/wavelet1.pdf +* http://grail.cs.washington.edu/projects/wavelets/article/wavelet2.pdf +* The basic of all Wavelet transforms is to decompose a signal into +* approximation (a) and detail (d) coefficients where the detail tends to be +* small or zero which allows / simplifies compression. The following "graphs" +* demonstrate the transform for a signal +* of length eight. The index always describes the decomposition level where +* a coefficient arises. The input signal is interpreted as approximation signal +* at level 0. The coefficients computed on the device are stored in the same +* scheme as in the example. This data structure is particularly well suited for +* compression and also preserves the hierarchical structure of the +decomposition. + +------------------------------------------------- +| a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | +------------------------------------------------- + +------------------------------------------------- +| a_1 | a_1 | a_1 | a_1 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +------------------------------------------------- +| a_2 | a_2 | d_2 | d_2 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +------------------------------------------------- +| a_3 | d_3 | d_2 | d_2 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +* Host code. +*/ + +#ifdef _WIN32 +#define NOMINMAX +#endif + +// includes, system +#include +#include +#include +#include +#include + +// includes, project +#include "helper_cuda.h" +#include "helper_functions.h" + +// constants which are used in host and device code +#define INV_SQRT_2 0.70710678118654752440f; +const unsigned int LOG_NUM_BANKS = 4; +const unsigned int NUM_BANKS = 16; + +//////////////////////////////////////////////////////////////////////////////// +// includes, kernels +#include "dwtHaar1D_kernel.cuh" + +//////////////////////////////////////////////////////////////////////////////// +// declaration, forward +void runTest(int argc, char **argv); +bool getLevels(unsigned int len, unsigned int *levels); + +//////////////////////////////////////////////////////////////////////////////// +// Program main +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + // run test + runTest(argc, argv); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Perform the wavelet decomposition +//////////////////////////////////////////////////////////////////////////////// +void runTest(int argc, char **argv) +{ + bool bResult = false; // flag for final validation of the results + + char *s_fname = NULL, *r_gold_fname = NULL; + char r_fname[256]; + const char usage[] = {"\nUsage:\n" + " dwtHaar1D --signal= --result= " + "--gold=\n\n" + " Input file containing the signal\n" + " Output file storing the result of the wavelet " + "decomposition\n" + " Input file containing the reference result of the " + "wavelet decomposition\n" + "\nExample:\n" + " ./dwtHaar1D\n" + " --signal=signal.dat\n" + " --result=result.dat\n" + " --gold=regression.gold.dat\n"}; + + printf("%s Starting...\n\n", argv[0]); + + // use command-line specified CUDA device, otherwise use device with highest + // Gflops/s + findCudaDevice(argc, (const char **)argv); + + // file names, either specified as cmd line args or use default + if (argc == 4) { + char *tmp_sfname, *tmp_rfname, *tmp_goldfname; + + if ((getCmdLineArgumentString(argc, (const char **)argv, "signal", &tmp_sfname) != true) + || (getCmdLineArgumentString(argc, (const char **)argv, "result", &tmp_rfname) != true) + || (getCmdLineArgumentString(argc, (const char **)argv, "gold", &tmp_goldfname) != true)) { + fprintf(stderr, "Invalid input syntax.\n%s", usage); + exit(EXIT_FAILURE); + } + + s_fname = sdkFindFilePath(tmp_sfname, argv[0]); + r_gold_fname = sdkFindFilePath(tmp_goldfname, argv[0]); + strcpy(r_fname, tmp_rfname); + } + else { + s_fname = sdkFindFilePath("signal.dat", argv[0]); + r_gold_fname = sdkFindFilePath("regression.gold.dat", argv[0]); + strcpy(r_fname, "result.dat"); + } + + printf("source file = \"%s\"\n", s_fname); + printf("reference file = \"%s\"\n", r_fname); + printf("gold file = \"%s\"\n", r_gold_fname); + + // read in signal + unsigned int slength = 0; + float *signal = NULL; + + if (s_fname == NULL) { + fprintf(stderr, "Cannot find the file containing the signal.\n%s", usage); + + exit(EXIT_FAILURE); + } + + if (sdkReadFile(s_fname, &signal, &slength, false) == true) { + printf("Reading signal from \"%s\"\n", s_fname); + } + else { + exit(EXIT_FAILURE); + } + + // get the number of decompositions necessary to perform a full decomposition + unsigned int dlevels_complete = 0; + + if (true != getLevels(slength, &dlevels_complete)) { + // error message + fprintf(stderr, "Signal length not supported.\n"); + // cleanup and abort + free(signal); + exit(EXIT_FAILURE); + } + + // device in data + float *d_idata = NULL; + // device out data + float *d_odata = NULL; + // device approx_final data + float *approx_final = NULL; + // The very final approximation coefficient has to be written to the output + // data, all others are reused as input data in the next global step and + // therefore have to be written to the input data again. + // The following flag indicates where to copy approx_final data + // - 0 is input, 1 is output + int approx_is_input; + + // allocate device mem + const unsigned int smem_size = sizeof(float) * slength; + checkCudaErrors(cudaMalloc((void **)&d_idata, smem_size)); + checkCudaErrors(cudaMalloc((void **)&d_odata, smem_size)); + checkCudaErrors(cudaMalloc((void **)&approx_final, smem_size)); + // copy input data to device + checkCudaErrors(cudaMemcpy(d_idata, signal, smem_size, cudaMemcpyHostToDevice)); + + // total number of threads + // in the first decomposition step always one thread computes the average and + // detail signal for one pair of adjacent values + unsigned int num_threads_total_left = slength / 2; + // decomposition levels performed in the current / next step + unsigned int dlevels_step = dlevels_complete; + + // 1D signal so the arrangement of elements is also 1D + dim3 block_size; + dim3 grid_size; + + // number of decomposition levels left after one iteration on the device + unsigned int dlevels_left = dlevels_complete; + + // if less or equal 1k elements, then the data can be processed in one block, + // this avoids the Wait-For-Idle (WFI) on host side which is necessary if the + // computation is split across multiple SM's if enough input data + if (dlevels_complete <= 10) { + // decomposition can be performed at once + block_size.x = num_threads_total_left; + approx_is_input = 0; + } + else { + // 512 threads per block + grid_size.x = (num_threads_total_left / 512); + block_size.x = 512; + + // 512 threads corresponds to 10 decomposition steps + dlevels_step = 10; + dlevels_left -= 10; + + approx_is_input = 1; + } + + // Initialize d_odata to 0.0f + initValue<<>>(d_odata, 0.0f); + + // do until full decomposition is accomplished + while (0 != num_threads_total_left) { + // double the number of threads as bytes + unsigned int mem_shared = (2 * block_size.x) * sizeof(float); + // extra memory requirements to avoid bank conflicts + mem_shared += ((2 * block_size.x) / NUM_BANKS) * sizeof(float); + + // run kernel + dwtHaar1D<<>>( + d_idata, d_odata, approx_final, dlevels_step, num_threads_total_left, block_size.x); + + // Copy approx_final to appropriate location + if (approx_is_input) { + checkCudaErrors(cudaMemcpy(d_idata, approx_final, grid_size.x * 4, cudaMemcpyDeviceToDevice)); + } + else { + checkCudaErrors(cudaMemcpy(d_odata, approx_final, grid_size.x * 4, cudaMemcpyDeviceToDevice)); + } + + // update level variables + if (dlevels_left < 10) { + // approx_final = d_odata; + approx_is_input = 0; + } + + // more global steps necessary + dlevels_step = (dlevels_left > 10) ? dlevels_left - 10 : dlevels_left; + dlevels_left -= 10; + + // after each step only half the threads are used any longer + // therefore after 10 steps 2^10 less threads + num_threads_total_left = num_threads_total_left >> 10; + + // update block and grid size + grid_size.x = (num_threads_total_left / 512) + (0 != (num_threads_total_left % 512)) ? 1 : 0; + + if (grid_size.x <= 1) { + block_size.x = num_threads_total_left; + } + } + + // get the result back from the server + // allocate mem for the result + float *odata = (float *)malloc(smem_size); + checkCudaErrors(cudaMemcpy(odata, d_odata, smem_size, cudaMemcpyDeviceToHost)); + + // post processing + // write file for regression test + if (r_fname == NULL) { + fprintf(stderr, + "Cannot write the output file storing the result of the wavelet " + "decomposition.\n%s", + usage); + exit(EXIT_FAILURE); + } + + if (sdkWriteFile(r_fname, odata, slength, 0.001f, false) == true) { + printf("Writing result to \"%s\"\n", r_fname); + } + else { + exit(EXIT_FAILURE); + } + + // load the reference solution + unsigned int len_reference = 0; + float *reference = NULL; + + if (r_gold_fname == NULL) { + fprintf(stderr, + "Cannot read the file containing the reference result of the " + "wavelet decomposition.\n%s", + usage); + + exit(EXIT_FAILURE); + } + + if (sdkReadFile(r_gold_fname, &reference, &len_reference, false) == true) { + printf("Reading reference result from \"%s\"\n", r_gold_fname); + } + else { + exit(EXIT_FAILURE); + } + + assert(slength == len_reference); + + // compare the computed solution and the reference + bResult = (bool)sdkCompareL2fe(reference, odata, slength, 0.001f); + free(reference); + + // free allocated host and device memory + checkCudaErrors(cudaFree(d_odata)); + checkCudaErrors(cudaFree(d_idata)); + checkCudaErrors(cudaFree(approx_final)); + + free(signal); + free(odata); + free(s_fname); + free(r_gold_fname); + + printf(bResult ? "Test success!\n" : "Test failure!\n"); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Get number of decomposition levels to perform a full decomposition +//! Also check if the input signal size is suitable +//! @return true if the number of decomposition levels could be determined +//! and the signal length is supported by the implementation, +//! otherwise false +//! @param len length of input signal +//! @param levels number of decomposition levels necessary to perform a full +//! decomposition +//////////////////////////////////////////////////////////////////////////////// +bool getLevels(unsigned int len, unsigned int *levels) +{ + bool retval = false; + + // currently signals up to a length of 2^20 supported + for (unsigned int i = 0; i < 20; ++i) { + if (len == (1 << i)) { + *levels = i; + retval = true; + break; + } + } + + return retval; +} diff --git a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh new file mode 100644 index 000000000..3bf1e6588 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh @@ -0,0 +1,248 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* +* 1D DWT for Haar wavelet and signals with a length which is a power of 2. +* The code reduces bank conflicts and non-coalesced reads / writes as +* appropriate but does not fully remove them because the computational +* overhead to achieve this would outweighs the benefit (see inline comments +* for more details). +* Large signals are subdivided into sub-signals with 512 elements and the +* wavelet transform for these is computed with one block over 10 decomposition +* levels. The resulting signal consisting of the approximation coefficients at +* level X is then processed in a subsequent step on the device. This requires +* interblock synchronization which is only possible on host side. +* Detail coefficients which have been computed are not further referenced +* during the decomposition so that they can be stored directly in their final +* position in global memory. The transform and its storing scheme preserve +* locality in the coefficients so that these writes are coalesced. +* Approximation coefficients are stored in shared memory because they are +* needed to compute the subsequent decomposition step. The top most +* approximation coefficient for a sub-signal processed by one block is stored +* in a special global memory location to simplify the processing after the +* interblock synchronization. +* Most books on wavelets explain the Haar wavelet decomposition. A good freely +* available resource is the Wavelet primer by Stollnitz et al. +* http://grail.cs.washington.edu/projects/wavelets/article/wavelet1.pdf +* http://grail.cs.washington.edu/projects/wavelets/article/wavelet2.pdf +* The basic of all Wavelet transforms is to decompose a signal into +* approximation (a) and detail (d) coefficients where the detail tends to be +* small or zero which allows / simplifies compression. The following "graphs" +* demonstrate the transform for a signal +* of length eight. The index always describes the decomposition level where +* a coefficient arises. The input signal is interpreted as approximation signal +* at level 0. The coefficients computed on the device are stored in the same +* scheme as in the example. This data structure is particularly well suited for +* compression and also preserves the hierarchical structure of the +decomposition. + +------------------------------------------------- +| a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | a_0 | +------------------------------------------------- + +------------------------------------------------- +| a_1 | a_1 | a_1 | a_1 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +------------------------------------------------- +| a_2 | a_2 | d_2 | d_2 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +------------------------------------------------- +| a_3 | d_3 | d_2 | d_2 | d_1 | d_1 | d_1 | d_1 | +------------------------------------------------- + +* Device Code. +*/ + +#ifndef _DWTHAAR1D_KERNEL_H_ +#define _DWTHAAR1D_KERNEL_H_ + +#include + +namespace cg = cooperative_groups; + +//////////////////////////////////////////////////////////////////////////////// +//! @param id input data +//! @param od output data +//! @param value +//////////////////////////////////////////////////////////////////////////////// +__global__ void initValue(float *od, float value) +{ + // Handle to thread block group + cg::thread_block cta = cg::this_thread_block(); + // position of write into global memory + unsigned int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + od[index] = value; + + // sync after each decomposition step + cg::sync(cta); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Compute partial wavelet decomposition on the GPU using Haar basis +//! For each thread block the full decomposition is computed but these results +//! have to be combined +//! Use one thread to perform the full decomposition +//! @param id input data +//! @param od output data +//! @param approx_final place to store the final approximation coefficient for +//! the subsignal +//! @param dlevels number of decomposition levels for this transform +//! @param slength_step_half half signal length for current decomposition +//! level (offset for storing detail coefficients in +//! global memory +//! @param bdim block dimension +//////////////////////////////////////////////////////////////////////////////// +__global__ void dwtHaar1D(float *id, + float *od, + float *approx_final, + const unsigned int dlevels, + const unsigned int slength_step_half, + const int bdim) +{ + // Handle to thread block group + cg::thread_block cta = cg::this_thread_block(); + + // shared memory for part of the signal + extern __shared__ float shared[]; + + // thread runtime environment, 1D parametrization + const int gdim = gridDim.x; + // const int bdim = blockDim.x; + const int bid = blockIdx.x; + const int tid = threadIdx.x; + + // global thread id (w.r.t. to total data set) + const int tid_global = (bid * bdim) + tid; + unsigned int idata = (bid * (2 * bdim)) + tid; + + // read data from global memory + shared[tid] = id[idata]; + shared[tid + bdim] = id[idata + bdim]; + cg::sync(cta); + + // this operation has a two way bank conflicts for all threads, this are two + // additional cycles for each warp -- all alternatives to avoid this bank + // conflict are more expensive than the one cycle introduced by serialization + float data0 = shared[2 * tid]; + float data1 = shared[(2 * tid) + 1]; + cg::sync(cta); + + // detail coefficient, not further referenced so directly store in + // global memory + od[tid_global + slength_step_half] = (data0 - data1) * INV_SQRT_2; + + // offset to avoid bank conflicts + // see the scan example for a more detailed description + unsigned int atid = tid + (tid >> LOG_NUM_BANKS); + + // approximation coefficient + // store in shared memory for further decomposition steps in this global step + shared[atid] = (data0 + data1) * INV_SQRT_2; + + // all threads have to write approximation coefficient to shared memory before + // next steps can take place + cg::sync(cta); + + // early out if possible + // the compiler removes this part from the source because dlevels is + // a constant shader input + // note: syncthreads in bodies of branches can lead to dead-locks unless + // the condition evaluates the same way for ALL threads of a block, as in + // this case + if (dlevels > 1) { + // offset to second element in shared element which has to be used for the + // decomposition, effectively 2^(i - 1) + unsigned int offset_neighbor = 1; + // number of active threads per decomposition level + // identical to the offset for the detail coefficients + unsigned int num_threads = bdim >> 1; + + // index for the first element of the pair to process + // the representation is still compact (and therefore still tid * 2) + // because the first step operated on registers and only the result has been + // written to shared memory + unsigned int idata0 = tid * 2; + + // offset levels to make the loop more efficient + for (unsigned int i = 1; i < dlevels; ++i) { + // Non-coalesced writes occur if the number of active threads becomes + // less than 16 for a block because the start address for the first + // block is not always aligned with 64 byte which is necessary for + // coalesced access. However, the problem only occurs at high levels + // with only a small number of active threads so that the total number of + // non-coalesced access is rather small and does not justify the + // computations which are necessary to avoid these uncoalesced writes + // (this has been tested and verified) + if (tid < num_threads) { + // update stride, with each decomposition level the stride grows by a + // factor of 2 + unsigned int idata1 = idata0 + offset_neighbor; + + // position of write into global memory + unsigned int g_wpos = (num_threads * gdim) + (bid * num_threads) + tid; + + // compute wavelet decomposition step + + // offset to avoid bank conflicts + unsigned int c_idata0 = idata0 + (idata0 >> LOG_NUM_BANKS); + unsigned int c_idata1 = idata1 + (idata1 >> LOG_NUM_BANKS); + + // detail coefficient, not further modified so directly store + // in global memory + od[g_wpos] = (shared[c_idata0] - shared[c_idata1]) * INV_SQRT_2; + + // approximation coefficient + // note that the representation in shared memory becomes rather sparse + // (with a lot of holes inbetween) but the storing scheme in global + // memory guarantees that the common representation (approx, detail_0, + // detail_1, ...) + // is achieved + shared[c_idata0] = (shared[c_idata0] + shared[c_idata1]) * INV_SQRT_2; + + // update storage offset for details + num_threads = num_threads >> 1; // div 2 + offset_neighbor <<= 1; // mul 2 + idata0 = idata0 << 1; // mul 2 + } + + // sync after each decomposition step + cg::sync(cta); + } + + // write the top most level element for the next decomposition steps + // which are performed after an interlock synchronization on host side + if (0 == tid) { + approx_final[bid] = shared[0]; + } + + } // end early out if possible +} + +#endif // #ifndef _DWTHAAR1D_KERNEL_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/exception.h b/src/cuda/H100/image/dwtHaar1D/exception.h new file mode 100644 index 000000000..ca8ac2525 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/exception.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* CUda UTility Library */ +#ifndef COMMON_EXCEPTION_H_ +#define COMMON_EXCEPTION_H_ + +// includes, system +#include +#include +#include +#include +#include + +//! Exception wrapper. +//! @param Std_Exception Exception out of namespace std for easy typing. +template +class Exception : public Std_Exception { + public: + //! @brief Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const char *detailed = "-"); + + //! Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const std::string &detailed); + + //! Destructor + virtual ~Exception() throw(); + + private: + //! Constructor, default (private) + Exception(); + + //! Constructor, standard + //! @param str string returned by what() + explicit Exception(const std::string &str); +}; + +//////////////////////////////////////////////////////////////////////////////// +//! Exception handler function for arbitrary exceptions +//! @param ex exception to handle +//////////////////////////////////////////////////////////////////////////////// +template +inline void handleException(const Exception_Typ &ex) { + std::cerr << ex.what() << std::endl; + + exit(EXIT_FAILURE); +} + +//! Convenience macros + +//! Exception caused by dynamic program behavior, e.g. file does not exist +#define RUNTIME_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Logic exception in program, e.g. an assert failed +#define LOGIC_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Out of range exception +#define RANGE_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//////////////////////////////////////////////////////////////////////////////// +//! Implementation + +// includes, system +#include + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const char *detailed) { + std::stringstream s; + + // Quiet heavy-weight but exceptions are not for + // performance / release versions + s << "Exception in file '" << file << "' in line " << line << "\n" + << "Detailed description: " << detailed << "\n"; + + throw Exception(s.str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const std::string &msg) { + throw_it(file, line, msg.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, default (private). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception() : Std_Exception("Unknown Exception.\n") {} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, standard (private). +//! String returned by what(). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception(const std::string &s) : Std_Exception(s) {} + +//////////////////////////////////////////////////////////////////////////////// +//! Destructor +//////////////////////////////////////////////////////////////////////////////// +template +Exception::~Exception() throw() {} + + // functions, exported + +#endif // COMMON_EXCEPTION_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/helper_cuda.h b/src/cuda/H100/image/dwtHaar1D/helper_cuda.h new file mode 100644 index 000000000..723b28f7a --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/helper_cuda.h @@ -0,0 +1,988 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//////////////////////////////////////////////////////////////////////////////// +// These are CUDA Helper functions for initialization and error checking + +#ifndef COMMON_HELPER_CUDA_H_ +#define COMMON_HELPER_CUDA_H_ + +#pragma once + +#include +#include +#include +#include + +#include "helper_string.h" + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// Note, it is required that your SDK sample to include the proper header +// files, please refer the CUDA examples for examples of the needed CUDA +// headers, which may change depending on which CUDA functions are used. + +// CUDA Runtime error messages +#ifdef __DRIVER_TYPES_H__ +static const char *_cudaGetErrorEnum(cudaError_t error) { + return cudaGetErrorName(error); +} +#endif + +#ifdef CUDA_DRIVER_API +// CUDA Driver API errors +static const char *_cudaGetErrorEnum(CUresult error) { + static char unknown[] = ""; + const char *ret = NULL; + cuGetErrorName(error, &ret); + return ret ? ret : unknown; +} +#endif + +#ifdef CUBLAS_API_H_ +// cuBLAS API errors +static const char *_cudaGetErrorEnum(cublasStatus_t error) { + switch (error) { + case CUBLAS_STATUS_SUCCESS: + return "CUBLAS_STATUS_SUCCESS"; + + case CUBLAS_STATUS_NOT_INITIALIZED: + return "CUBLAS_STATUS_NOT_INITIALIZED"; + + case CUBLAS_STATUS_ALLOC_FAILED: + return "CUBLAS_STATUS_ALLOC_FAILED"; + + case CUBLAS_STATUS_INVALID_VALUE: + return "CUBLAS_STATUS_INVALID_VALUE"; + + case CUBLAS_STATUS_ARCH_MISMATCH: + return "CUBLAS_STATUS_ARCH_MISMATCH"; + + case CUBLAS_STATUS_MAPPING_ERROR: + return "CUBLAS_STATUS_MAPPING_ERROR"; + + case CUBLAS_STATUS_EXECUTION_FAILED: + return "CUBLAS_STATUS_EXECUTION_FAILED"; + + case CUBLAS_STATUS_INTERNAL_ERROR: + return "CUBLAS_STATUS_INTERNAL_ERROR"; + + case CUBLAS_STATUS_NOT_SUPPORTED: + return "CUBLAS_STATUS_NOT_SUPPORTED"; + + case CUBLAS_STATUS_LICENSE_ERROR: + return "CUBLAS_STATUS_LICENSE_ERROR"; + } + + return ""; +} +#endif + +#ifdef _CUFFT_H_ +// cuFFT API errors +static const char *_cudaGetErrorEnum(cufftResult error) { + switch (error) { + case CUFFT_SUCCESS: + return "CUFFT_SUCCESS"; + + case CUFFT_INVALID_PLAN: + return "CUFFT_INVALID_PLAN"; + + case CUFFT_ALLOC_FAILED: + return "CUFFT_ALLOC_FAILED"; + + case CUFFT_INVALID_TYPE: + return "CUFFT_INVALID_TYPE"; + + case CUFFT_INVALID_VALUE: + return "CUFFT_INVALID_VALUE"; + + case CUFFT_INTERNAL_ERROR: + return "CUFFT_INTERNAL_ERROR"; + + case CUFFT_EXEC_FAILED: + return "CUFFT_EXEC_FAILED"; + + case CUFFT_SETUP_FAILED: + return "CUFFT_SETUP_FAILED"; + + case CUFFT_INVALID_SIZE: + return "CUFFT_INVALID_SIZE"; + + case CUFFT_UNALIGNED_DATA: + return "CUFFT_UNALIGNED_DATA"; + + case CUFFT_INVALID_DEVICE: + return "CUFFT_INVALID_DEVICE"; + + case CUFFT_NO_WORKSPACE: + return "CUFFT_NO_WORKSPACE"; + + case CUFFT_NOT_IMPLEMENTED: + return "CUFFT_NOT_IMPLEMENTED"; + + case CUFFT_NOT_SUPPORTED: + return "CUFFT_NOT_SUPPORTED"; + + case CUFFT_MISSING_DEPENDENCY: + return "CUFFT_MISSING_DEPENDENCY"; + + case CUFFT_NVRTC_FAILURE: + return "CUFFT_NVRTC_FAILURE"; + + case CUFFT_NVJITLINK_FAILURE: + return "CUFFT_NVJITLINK_FAILURE"; + + case CUFFT_NVSHMEM_FAILURE: + return "CUFFT_NVSHMEM_FAILURE"; + } + + return ""; +} +#endif + +#ifdef CUSPARSEAPI +// cuSPARSE API errors +static const char *_cudaGetErrorEnum(cusparseStatus_t error) { + switch (error) { + case CUSPARSE_STATUS_SUCCESS: + return "CUSPARSE_STATUS_SUCCESS"; + + case CUSPARSE_STATUS_NOT_INITIALIZED: + return "CUSPARSE_STATUS_NOT_INITIALIZED"; + + case CUSPARSE_STATUS_ALLOC_FAILED: + return "CUSPARSE_STATUS_ALLOC_FAILED"; + + case CUSPARSE_STATUS_INVALID_VALUE: + return "CUSPARSE_STATUS_INVALID_VALUE"; + + case CUSPARSE_STATUS_ARCH_MISMATCH: + return "CUSPARSE_STATUS_ARCH_MISMATCH"; + + case CUSPARSE_STATUS_MAPPING_ERROR: + return "CUSPARSE_STATUS_MAPPING_ERROR"; + + case CUSPARSE_STATUS_EXECUTION_FAILED: + return "CUSPARSE_STATUS_EXECUTION_FAILED"; + + case CUSPARSE_STATUS_INTERNAL_ERROR: + return "CUSPARSE_STATUS_INTERNAL_ERROR"; + + case CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + } + + return ""; +} +#endif + +#ifdef CUSOLVER_COMMON_H_ +// cuSOLVER API errors +static const char *_cudaGetErrorEnum(cusolverStatus_t error) { + switch (error) { + case CUSOLVER_STATUS_SUCCESS: + return "CUSOLVER_STATUS_SUCCESS"; + case CUSOLVER_STATUS_NOT_INITIALIZED: + return "CUSOLVER_STATUS_NOT_INITIALIZED"; + case CUSOLVER_STATUS_ALLOC_FAILED: + return "CUSOLVER_STATUS_ALLOC_FAILED"; + case CUSOLVER_STATUS_INVALID_VALUE: + return "CUSOLVER_STATUS_INVALID_VALUE"; + case CUSOLVER_STATUS_ARCH_MISMATCH: + return "CUSOLVER_STATUS_ARCH_MISMATCH"; + case CUSOLVER_STATUS_MAPPING_ERROR: + return "CUSOLVER_STATUS_MAPPING_ERROR"; + case CUSOLVER_STATUS_EXECUTION_FAILED: + return "CUSOLVER_STATUS_EXECUTION_FAILED"; + case CUSOLVER_STATUS_INTERNAL_ERROR: + return "CUSOLVER_STATUS_INTERNAL_ERROR"; + case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + case CUSOLVER_STATUS_NOT_SUPPORTED: + return "CUSOLVER_STATUS_NOT_SUPPORTED "; + case CUSOLVER_STATUS_ZERO_PIVOT: + return "CUSOLVER_STATUS_ZERO_PIVOT"; + case CUSOLVER_STATUS_INVALID_LICENSE: + return "CUSOLVER_STATUS_INVALID_LICENSE"; + } + + return ""; +} +#endif + +#ifdef CURAND_H_ +// cuRAND API errors +static const char *_cudaGetErrorEnum(curandStatus_t error) { + switch (error) { + case CURAND_STATUS_SUCCESS: + return "CURAND_STATUS_SUCCESS"; + + case CURAND_STATUS_VERSION_MISMATCH: + return "CURAND_STATUS_VERSION_MISMATCH"; + + case CURAND_STATUS_NOT_INITIALIZED: + return "CURAND_STATUS_NOT_INITIALIZED"; + + case CURAND_STATUS_ALLOCATION_FAILED: + return "CURAND_STATUS_ALLOCATION_FAILED"; + + case CURAND_STATUS_TYPE_ERROR: + return "CURAND_STATUS_TYPE_ERROR"; + + case CURAND_STATUS_OUT_OF_RANGE: + return "CURAND_STATUS_OUT_OF_RANGE"; + + case CURAND_STATUS_LENGTH_NOT_MULTIPLE: + return "CURAND_STATUS_LENGTH_NOT_MULTIPLE"; + + case CURAND_STATUS_DOUBLE_PRECISION_REQUIRED: + return "CURAND_STATUS_DOUBLE_PRECISION_REQUIRED"; + + case CURAND_STATUS_LAUNCH_FAILURE: + return "CURAND_STATUS_LAUNCH_FAILURE"; + + case CURAND_STATUS_PREEXISTING_FAILURE: + return "CURAND_STATUS_PREEXISTING_FAILURE"; + + case CURAND_STATUS_INITIALIZATION_FAILED: + return "CURAND_STATUS_INITIALIZATION_FAILED"; + + case CURAND_STATUS_ARCH_MISMATCH: + return "CURAND_STATUS_ARCH_MISMATCH"; + + case CURAND_STATUS_INTERNAL_ERROR: + return "CURAND_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NVJPEGAPI +// nvJPEG API errors +static const char *_cudaGetErrorEnum(nvjpegStatus_t error) { + switch (error) { + case NVJPEG_STATUS_SUCCESS: + return "NVJPEG_STATUS_SUCCESS"; + + case NVJPEG_STATUS_NOT_INITIALIZED: + return "NVJPEG_STATUS_NOT_INITIALIZED"; + + case NVJPEG_STATUS_INVALID_PARAMETER: + return "NVJPEG_STATUS_INVALID_PARAMETER"; + + case NVJPEG_STATUS_BAD_JPEG: + return "NVJPEG_STATUS_BAD_JPEG"; + + case NVJPEG_STATUS_JPEG_NOT_SUPPORTED: + return "NVJPEG_STATUS_JPEG_NOT_SUPPORTED"; + + case NVJPEG_STATUS_ALLOCATOR_FAILURE: + return "NVJPEG_STATUS_ALLOCATOR_FAILURE"; + + case NVJPEG_STATUS_EXECUTION_FAILED: + return "NVJPEG_STATUS_EXECUTION_FAILED"; + + case NVJPEG_STATUS_ARCH_MISMATCH: + return "NVJPEG_STATUS_ARCH_MISMATCH"; + + case NVJPEG_STATUS_INTERNAL_ERROR: + return "NVJPEG_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NV_NPPIDEFS_H +// NPP API errors +static const char *_cudaGetErrorEnum(NppStatus error) { + switch (error) { + case NPP_NOT_SUPPORTED_MODE_ERROR: + return "NPP_NOT_SUPPORTED_MODE_ERROR"; + + case NPP_ROUND_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ROUND_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_RESIZE_NO_OPERATION_ERROR: + return "NPP_RESIZE_NO_OPERATION_ERROR"; + + case NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY: + return "NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_BAD_ARG_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFF_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECT_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUAD_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEM_ALLOC_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTO_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_INPUT: + return "NPP_INVALID_INPUT"; + + case NPP_POINTER_ERROR: + return "NPP_POINTER_ERROR"; + + case NPP_WARNING: + return "NPP_WARNING"; + + case NPP_ODD_ROI_WARNING: + return "NPP_ODD_ROI_WARNING"; +#else + + // These are for CUDA 5.5 or higher + case NPP_BAD_ARGUMENT_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFFICIENT_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECTANGLE_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUADRANGLE_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEMORY_ALLOCATION_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_HOST_POINTER_ERROR: + return "NPP_INVALID_HOST_POINTER_ERROR"; + + case NPP_INVALID_DEVICE_POINTER_ERROR: + return "NPP_INVALID_DEVICE_POINTER_ERROR"; +#endif + + case NPP_LUT_NUMBER_OF_LEVELS_ERROR: + return "NPP_LUT_NUMBER_OF_LEVELS_ERROR"; + + case NPP_TEXTURE_BIND_ERROR: + return "NPP_TEXTURE_BIND_ERROR"; + + case NPP_WRONG_INTERSECTION_ROI_ERROR: + return "NPP_WRONG_INTERSECTION_ROI_ERROR"; + + case NPP_NOT_EVEN_STEP_ERROR: + return "NPP_NOT_EVEN_STEP_ERROR"; + + case NPP_INTERPOLATION_ERROR: + return "NPP_INTERPOLATION_ERROR"; + + case NPP_RESIZE_FACTOR_ERROR: + return "NPP_RESIZE_FACTOR_ERROR"; + + case NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR: + return "NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_MEMFREE_ERR: + return "NPP_MEMFREE_ERR"; + + case NPP_MEMSET_ERR: + return "NPP_MEMSET_ERR"; + + case NPP_MEMCPY_ERR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERR: + return "NPP_MIRROR_FLIP_ERR"; +#else + + case NPP_MEMFREE_ERROR: + return "NPP_MEMFREE_ERROR"; + + case NPP_MEMSET_ERROR: + return "NPP_MEMSET_ERROR"; + + case NPP_MEMCPY_ERROR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERROR: + return "NPP_MIRROR_FLIP_ERROR"; +#endif + + case NPP_ALIGNMENT_ERROR: + return "NPP_ALIGNMENT_ERROR"; + + case NPP_STEP_ERROR: + return "NPP_STEP_ERROR"; + + case NPP_SIZE_ERROR: + return "NPP_SIZE_ERROR"; + + case NPP_NULL_POINTER_ERROR: + return "NPP_NULL_POINTER_ERROR"; + + case NPP_CUDA_KERNEL_EXECUTION_ERROR: + return "NPP_CUDA_KERNEL_EXECUTION_ERROR"; + + case NPP_NOT_IMPLEMENTED_ERROR: + return "NPP_NOT_IMPLEMENTED_ERROR"; + + case NPP_ERROR: + return "NPP_ERROR"; + + case NPP_SUCCESS: + return "NPP_SUCCESS"; + + case NPP_WRONG_INTERSECTION_QUAD_WARNING: + return "NPP_WRONG_INTERSECTION_QUAD_WARNING"; + + case NPP_MISALIGNED_DST_ROI_WARNING: + return "NPP_MISALIGNED_DST_ROI_WARNING"; + + case NPP_AFFINE_QUAD_INCORRECT_WARNING: + return "NPP_AFFINE_QUAD_INCORRECT_WARNING"; + + case NPP_DOUBLE_SIZE_WARNING: + return "NPP_DOUBLE_SIZE_WARNING"; + + case NPP_WRONG_INTERSECTION_ROI_WARNING: + return "NPP_WRONG_INTERSECTION_ROI_WARNING"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x6000 + /* These are 6.0 or higher */ + case NPP_LUT_PALETTE_BITSIZE_ERROR: + return "NPP_LUT_PALETTE_BITSIZE_ERROR"; + + case NPP_ZC_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ZC_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_QUALITY_INDEX_ERROR: + return "NPP_QUALITY_INDEX_ERROR"; + + case NPP_CHANNEL_ORDER_ERROR: + return "NPP_CHANNEL_ORDER_ERROR"; + + case NPP_ZERO_MASK_VALUE_ERROR: + return "NPP_ZERO_MASK_VALUE_ERROR"; + + case NPP_NUMBER_OF_CHANNELS_ERROR: + return "NPP_NUMBER_OF_CHANNELS_ERROR"; + + case NPP_COI_ERROR: + return "NPP_COI_ERROR"; + + case NPP_DIVISOR_ERROR: + return "NPP_DIVISOR_ERROR"; + + case NPP_CHANNEL_ERROR: + return "NPP_CHANNEL_ERROR"; + + case NPP_STRIDE_ERROR: + return "NPP_STRIDE_ERROR"; + + case NPP_ANCHOR_ERROR: + return "NPP_ANCHOR_ERROR"; + + case NPP_MASK_SIZE_ERROR: + return "NPP_MASK_SIZE_ERROR"; + + case NPP_MOMENT_00_ZERO_ERROR: + return "NPP_MOMENT_00_ZERO_ERROR"; + + case NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR: + return "NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR"; + + case NPP_THRESHOLD_ERROR: + return "NPP_THRESHOLD_ERROR"; + + case NPP_CONTEXT_MATCH_ERROR: + return "NPP_CONTEXT_MATCH_ERROR"; + + case NPP_FFT_FLAG_ERROR: + return "NPP_FFT_FLAG_ERROR"; + + case NPP_FFT_ORDER_ERROR: + return "NPP_FFT_ORDER_ERROR"; + + case NPP_SCALE_RANGE_ERROR: + return "NPP_SCALE_RANGE_ERROR"; + + case NPP_DATA_TYPE_ERROR: + return "NPP_DATA_TYPE_ERROR"; + + case NPP_OUT_OFF_RANGE_ERROR: + return "NPP_OUT_OFF_RANGE_ERROR"; + + case NPP_DIVIDE_BY_ZERO_ERROR: + return "NPP_DIVIDE_BY_ZERO_ERROR"; + + case NPP_RANGE_ERROR: + return "NPP_RANGE_ERROR"; + + case NPP_NO_MEMORY_ERROR: + return "NPP_NO_MEMORY_ERROR"; + + case NPP_ERROR_RESERVED: + return "NPP_ERROR_RESERVED"; + + case NPP_NO_OPERATION_WARNING: + return "NPP_NO_OPERATION_WARNING"; + + case NPP_DIVIDE_BY_ZERO_WARNING: + return "NPP_DIVIDE_BY_ZERO_WARNING"; +#endif + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x7000 + /* These are 7.0 or higher */ + case NPP_OVERFLOW_ERROR: + return "NPP_OVERFLOW_ERROR"; + + case NPP_CORRUPTED_DATA_ERROR: + return "NPP_CORRUPTED_DATA_ERROR"; +#endif + } + + return ""; +} +#endif + +template +void check(T result, char const *const func, const char *const file, + int const line) { + if (result) { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \"%s\" \n", file, line, + static_cast(result), _cudaGetErrorEnum(result), func); + exit(EXIT_FAILURE); + } +} + +#ifdef __DRIVER_TYPES_H__ +// This will output the proper CUDA error strings in the event +// that a CUDA host call returns an error +#define checkCudaErrors(val) check((val), #val, __FILE__, __LINE__) + +// This will output the proper error string when calling cudaGetLastError +#define getLastCudaError(msg) __getLastCudaError(msg, __FILE__, __LINE__) + +inline void __getLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + exit(EXIT_FAILURE); + } +} + +// This will only print the proper error string when calling cudaGetLastError +// but not exit program incase error detected. +#define printLastCudaError(msg) __printLastCudaError(msg, __FILE__, __LINE__) + +inline void __printLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + } +} +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif + +// Float To Int conversion +inline int ftoi(float value) { + return (value >= 0 ? static_cast(value + 0.5) + : static_cast(value - 0.5)); +} + +// Beginning of GPU Architecture definitions +inline int _ConvertSMVer2Cores(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the # of cores per SM + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + int Cores; + } sSMtoCores; + + sSMtoCores nGpuArchCoresPerSM[] = { + {0x30, 192}, + {0x32, 192}, + {0x35, 192}, + {0x37, 192}, + {0x50, 128}, + {0x52, 128}, + {0x53, 128}, + {0x60, 64}, + {0x61, 128}, + {0x62, 128}, + {0x70, 64}, + {0x72, 64}, + {0x75, 64}, + {0x80, 64}, + {0x86, 128}, + {0x87, 128}, + {0x89, 128}, + {0x90, 128}, + {0xa0, 128}, + {0xa1, 128}, + {0xa3, 128}, + {0xb0, 128}, + {0xc0, 128}, + {0xc1, 128}, + {-1, -1}}; + + int index = 0; + + while (nGpuArchCoresPerSM[index].SM != -1) { + if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)) { + return nGpuArchCoresPerSM[index].Cores; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoCores for SM %d.%d is undefined." + " Default to use %d Cores/SM\n", + major, minor, nGpuArchCoresPerSM[index - 1].Cores); + return nGpuArchCoresPerSM[index - 1].Cores; +} + +inline const char* _ConvertSMVer2ArchName(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the GPU Arch name) + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + const char* name; + } sSMtoArchName; + + sSMtoArchName nGpuArchNameSM[] = { + {0x30, "Kepler"}, + {0x32, "Kepler"}, + {0x35, "Kepler"}, + {0x37, "Kepler"}, + {0x50, "Maxwell"}, + {0x52, "Maxwell"}, + {0x53, "Maxwell"}, + {0x60, "Pascal"}, + {0x61, "Pascal"}, + {0x62, "Pascal"}, + {0x70, "Volta"}, + {0x72, "Xavier"}, + {0x75, "Turing"}, + {0x80, "Ampere"}, + {0x86, "Ampere"}, + {0x87, "Ampere"}, + {0x89, "Ada"}, + {0x90, "Hopper"}, + {0xa0, "Blackwell"}, + {0xa1, "Blackwell"}, + {0xa3, "Blackwell"}, + {0xb0, "Blackwell"}, + {0xc0, "Blackwell"}, + {0xc1, "Blackwell"}, + {-1, "Graphics Device"}}; + + int index = 0; + + while (nGpuArchNameSM[index].SM != -1) { + if (nGpuArchNameSM[index].SM == ((major << 4) + minor)) { + return nGpuArchNameSM[index].name; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoArchName for SM %d.%d is undefined." + " Default to use %s\n", + major, minor, nGpuArchNameSM[index - 1].name); + return nGpuArchNameSM[index - 1].name; +} + // end of GPU Architecture definitions + +#ifdef __CUDA_RUNTIME_H__ +// General GPU Device CUDA Initialization +inline int gpuDeviceInit(int devID) { + int device_count; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuDeviceInit() CUDA error: " + "no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + if (devID < 0) { + devID = 0; + } + + if (devID > device_count - 1) { + fprintf(stderr, "\n"); + fprintf(stderr, ">> %d CUDA capable GPU device(s) detected. <<\n", + device_count); + fprintf(stderr, + ">> gpuDeviceInit (-device=%d) is not a valid" + " GPU device. <<\n", + devID); + fprintf(stderr, "\n"); + return -devID; + } + + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + if (computeMode == cudaComputeModeProhibited) { + fprintf(stderr, + "Error: device is running in , no threads can use cudaSetDevice().\n"); + return -1; + } + + if (major < 1) { + fprintf(stderr, "gpuDeviceInit(): GPU device does not support CUDA.\n"); + exit(EXIT_FAILURE); + } + + checkCudaErrors(cudaSetDevice(devID)); + printf("gpuDeviceInit() CUDA Device [%d]: \"%s\n", devID, _ConvertSMVer2ArchName(major, minor)); + + return devID; +} + +// This function returns the best GPU (with maximum GFLOPS) +inline int gpuGetMaxGflopsDeviceId() { + int current_device = 0, sm_per_multiproc = 0; + int max_perf_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + uint64_t max_compute_perf = 0; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the best CUDA capable GPU device + current_device = 0; + + while (current_device < device_count) { + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + + // If this GPU is not running on Compute Mode prohibited, + // then we can add it to the list + if (computeMode != cudaComputeModeProhibited) { + if (major == 9999 && minor == 9999) { + sm_per_multiproc = 1; + } else { + sm_per_multiproc = + _ConvertSMVer2Cores(major, minor); + } + int multiProcessorCount = 0, clockRate = 0; + checkCudaErrors(cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, current_device)); + cudaError_t result = cudaDeviceGetAttribute(&clockRate, cudaDevAttrClockRate, current_device); + if (result != cudaSuccess) { + // If cudaDevAttrClockRate attribute is not supported we + // set clockRate as 1, to consider GPU with most SMs and CUDA Cores. + if(result == cudaErrorInvalidValue) { + clockRate = 1; + } + else { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \n", __FILE__, __LINE__, + static_cast(result), _cudaGetErrorEnum(result)); + exit(EXIT_FAILURE); + } + } + uint64_t compute_perf = (uint64_t)multiProcessorCount * sm_per_multiproc * clockRate; + + if (compute_perf > max_compute_perf) { + max_compute_perf = compute_perf; + max_perf_device = current_device; + } + } else { + devices_prohibited++; + } + + ++current_device; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " all devices have compute mode prohibited.\n"); + exit(EXIT_FAILURE); + } + + return max_perf_device; +} + +// Initialization code to find the best CUDA Device +inline int findCudaDevice(int argc, const char **argv) { + int devID = 0; + + // If the command-line has a device number specified, use it + if (checkCmdLineFlag(argc, argv, "device")) { + devID = getCmdLineArgumentInt(argc, argv, "device="); + + if (devID < 0) { + printf("Invalid command line parameter\n "); + exit(EXIT_FAILURE); + } else { + devID = gpuDeviceInit(devID); + + if (devID < 0) { + printf("exiting...\n"); + exit(EXIT_FAILURE); + } + } + } else { + // Otherwise pick the device with highest Gflops/s + devID = gpuGetMaxGflopsDeviceId(); + checkCudaErrors(cudaSetDevice(devID)); + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + devID, _ConvertSMVer2ArchName(major, minor), major, minor); + + } + + return devID; +} + +inline int findIntegratedGPU() { + int current_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, "CUDA error: no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the integrated GPU which is compute capable + while (current_device < device_count) { + int computeMode = -1, integrated = -1; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&integrated, cudaDevAttrIntegrated, current_device)); + // If GPU is integrated and is not running on Compute Mode prohibited, + // then cuda can map to GLES resource + if (integrated && (computeMode != cudaComputeModeProhibited)) { + checkCudaErrors(cudaSetDevice(current_device)); + + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + current_device, _ConvertSMVer2ArchName(major, minor), major, minor); + + return current_device; + } else { + devices_prohibited++; + } + + current_device++; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "CUDA error:" + " No GLES-CUDA Interop capable GPU found.\n"); + exit(EXIT_FAILURE); + } + + return -1; +} + +// General check for CUDA GPU SM Capabilities +inline bool checkCudaCapabilities(int major_version, int minor_version) { + int dev; + int major = 0, minor = 0; + + checkCudaErrors(cudaGetDevice(&dev)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, dev)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, dev)); + + if ((major > major_version) || + (major == major_version && + minor >= minor_version)) { + printf(" Device %d: <%16s >, Compute SM %d.%d detected\n", dev, + _ConvertSMVer2ArchName(major, minor), major, minor); + return true; + } else { + printf( + " No GPU device was found that can support " + "CUDA compute capability %d.%d.\n", + major_version, minor_version); + return false; + } +} +#endif + + // end of CUDA Helper Functions + +#endif // COMMON_HELPER_CUDA_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/helper_functions.h b/src/cuda/H100/image/dwtHaar1D/helper_functions.h new file mode 100644 index 000000000..f71da5761 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/helper_functions.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, +// timers, image helpers, etc) +#ifndef COMMON_HELPER_FUNCTIONS_H_ +#define COMMON_HELPER_FUNCTIONS_H_ + +#ifdef WIN32 +#pragma warning(disable : 4996) +#endif + +// includes, project +#include +#include "exception.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +// includes, timer, string parsing, image helpers +#include "helper_image.h" // helper functions for image compare, dump, data comparisons +#include "helper_string.h" // helper functions for string parsing +#include "helper_timer.h" // helper functions for timers + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#endif // COMMON_HELPER_FUNCTIONS_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/helper_image.h b/src/cuda/H100/image/dwtHaar1D/helper_image.h new file mode 100644 index 000000000..7e81b0cd8 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/helper_image.h @@ -0,0 +1,1001 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (image,bitmap) +#ifndef COMMON_HELPER_IMAGE_H_ +#define COMMON_HELPER_IMAGE_H_ + +#include +#include "exception.h" +#include +#include + +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a < b) ? a : b) +#endif +#ifndef MAX +#define MAX(a, b) ((a > b) ? a : b) +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#include "helper_string.h" + +// namespace unnamed (internal) +namespace helper_image_internal { +//! size of PGM file header +const unsigned int PGMHeaderSize = 0x40; + +// types + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterFromUByte; + +//! Data converter from unsigned char / unsigned byte +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val); + } +}; + +//! Data converter from unsigned char / unsigned byte to float +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val) / 255.0f; + } +}; + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterToUByte; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator (essentially a passthru + //! @return converted value + //! @param val value to convert + unsigned char operator()(const unsigned char &val) { return val; } +}; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + unsigned char operator()(const float &val) { + return static_cast(val * 255.0f); + } +}; +} // namespace helper_image_internal + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#else +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#endif + +inline bool __loadPPM(const char *file, unsigned char **data, unsigned int *w, + unsigned int *h, unsigned int *channels) { + FILE *fp = NULL; + + if (FOPEN_FAIL(FOPEN(fp, file, "rb"))) { + std::cerr << "__LoadPPM() : Failed to open file: " << file << std::endl; + return false; + } + + // check header + char header[helper_image_internal::PGMHeaderSize]; + + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" << std::endl; + return false; + } + + if (strncmp(header, "P5", 2) == 0) { + *channels = 1; + } else if (strncmp(header, "P6", 2) == 0) { + *channels = 3; + } else { + std::cerr << "__LoadPPM() : File is not a PPM or PGM image" << std::endl; + *channels = 0; + return false; + } + + // parse header, read maxval, width and height + unsigned int width = 0; + unsigned int height = 0; + unsigned int maxval = 0; + unsigned int i = 0; + + while (i < 3) { + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" + << std::endl; + return false; + } + + if (header[0] == '#') { + continue; + } + + if (i == 0) { + i += SSCANF(header, "%u %u %u", &width, &height, &maxval); + } else if (i == 1) { + i += SSCANF(header, "%u %u", &height, &maxval); + } else if (i == 2) { + i += SSCANF(header, "%u", &maxval); + } + } + + // check if given handle for the data is initialized + if (NULL != *data) { + if (*w != width || *h != height) { + std::cerr << "__LoadPPM() : Invalid image dimensions." << std::endl; + } + } else { + *data = (unsigned char *)malloc(sizeof(unsigned char) * width * height * + *channels); + *w = width; + *h = height; + } + + // read and close file + if (fread(*data, sizeof(unsigned char), width * height * *channels, fp) == + 0) { + std::cerr << "__LoadPPM() read data returned error." << std::endl; + } + + fclose(fp); + + return true; +} + +template +inline bool sdkLoadPGM(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = NULL; + unsigned int channels; + + if (true != __loadPPM(file, &idata, w, h, &channels)) { + return false; + } + + unsigned int size = *w * *h * channels; + + // initialize mem if necessary + // the correct size is checked / set in loadPGMc() + if (NULL == *data) { + *data = reinterpret_cast(malloc(sizeof(T) * size)); + } + + // copy and cast data + std::transform(idata, idata + size, *data, + helper_image_internal::ConverterFromUByte()); + + free(idata); + + return true; +} + +template +inline bool sdkLoadPPM4(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = reinterpret_cast(malloc(sizeof(T) * size * 4)); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool __savePPM(const char *file, unsigned char *data, unsigned int w, + unsigned int h, unsigned int channels) { + assert(NULL != data); + assert(w > 0); + assert(h > 0); + + std::fstream fh(file, std::fstream::out | std::fstream::binary); + + if (fh.bad()) { + std::cerr << "__savePPM() : Opening file failed." << std::endl; + return false; + } + + if (channels == 1) { + fh << "P5\n"; + } else if (channels == 3) { + fh << "P6\n"; + } else { + std::cerr << "__savePPM() : Invalid number of channels." << std::endl; + return false; + } + + fh << w << "\n" << h << "\n" << 0xff << std::endl; + + for (unsigned int i = 0; (i < (w * h * channels)) && fh.good(); ++i) { + fh << data[i]; + } + + fh.flush(); + + if (fh.bad()) { + std::cerr << "__savePPM() : Writing data failed." << std::endl; + return false; + } + + fh.close(); + + return true; +} + +template +inline bool sdkSavePGM(const char *file, T *data, unsigned int w, + unsigned int h) { + unsigned int size = w * h; + unsigned char *idata = (unsigned char *)malloc(sizeof(unsigned char) * size); + + std::transform(data, data + size, idata, + helper_image_internal::ConverterToUByte()); + + // write file + bool result = __savePPM(file, idata, w, h, 1); + + // cleanup + free(idata); + + return result; +} + +inline bool sdkSavePPM4ub(const char *file, unsigned char *data, unsigned int w, + unsigned int h) { + // strip 4th component + int size = w * h; + unsigned char *ndata = + (unsigned char *)malloc(sizeof(unsigned char) * size * 3); + unsigned char *ptr = ndata; + + for (int i = 0; i < size; i++) { + *ptr++ = *data++; + *ptr++ = *data++; + *ptr++ = *data++; + data++; + } + + bool result = __savePPM(file, ndata, w, h, 3); + free(ndata); + return result; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFile(const char *filename, T **data, unsigned int *len, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // intermediate storage for the data read + std::vector data_read; + + // open file for reading + FILE *fh = NULL; + + // check if filestream is valid + if (FOPEN_FAIL(FOPEN(fh, filename, "r"))) { + printf("Unable to open input file: %s\n", filename); + return false; + } + + // read all data elements + T token; + + while (!feof(fh)) { + fscanf(fh, "%f", &token); + data_read.push_back(token); + } + + // the last element is read twice + data_read.pop_back(); + fclose(fh); + + // check if the given handle is already initialized + if (NULL != *data) { + if (*len != data_read.size()) { + std::cerr << "sdkReadFile() : Initialized memory given but " + << "size mismatch with signal read " + << "(data read / data init = " << (unsigned int)data_read.size() + << " / " << *len << ")" << std::endl; + + return false; + } + } else { + // allocate storage for the data read + *data = reinterpret_cast(malloc(sizeof(T) * data_read.size())); + // store signal size + *len = static_cast(data_read.size()); + } + + // copy data + memcpy(*data, &data_read.front(), sizeof(T) * data_read.size()); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFileBlocks(const char *filename, T **data, unsigned int *len, + unsigned int block_num, unsigned int block_size, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // open file for reading + FILE *fh = fopen(filename, "rb"); + + if (fh == NULL && verbose) { + std::cerr << "sdkReadFile() : Opening file failed." << std::endl; + return false; + } + + // check if the given handle is already initialized + // allocate storage for the data read + data[block_num] = reinterpret_cast(malloc(block_size)); + + // read all data elements + fseek(fh, block_num * block_size, SEEK_SET); + *len = fread(data[block_num], sizeof(T), block_size / sizeof(T), fh); + + fclose(fh); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Write a data file \filename +//! @return true if writing the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data data to write +//! @param len number of data elements in data, -1 on error +//! @param epsilon epsilon for comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkWriteFile(const char *filename, const T *data, unsigned int len, + const S epsilon, bool verbose, bool append = false) { + assert(NULL != filename); + assert(NULL != data); + + // open file for writing + // if (append) { + std::fstream fh(filename, std::fstream::out | std::fstream::ate); + + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename + << " for write/append." << std::endl; + } + + /* } else { + std::fstream fh(filename, std::fstream::out); + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename << " for + write." << std::endl; + } + } + */ + + // check if filestream is valid + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Opening file failed." << std::endl; + } + + return false; + } + + // first write epsilon + fh << "# " << epsilon << "\n"; + + // write data + for (unsigned int i = 0; (i < len) && (fh.good()); ++i) { + fh << data[i] << ' '; + } + + // Check if writing succeeded + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Writing file failed." << std::endl; + } + + return false; + } + + // file ends with nl + fh << std::endl; + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference timer_interface to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareData(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + bool result = true; + unsigned int error_count = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = static_cast(reference[i]) - static_cast(data[i]); + bool comp = (diff <= epsilon) && (diff >= -epsilon); + result &= comp; + + error_count += !comp; + +#if 0 + + if (!comp) { + std::cerr << "ERROR, i = " << i << ",\t " + << reference[i] << " / " + << data[i] + << " (reference / data)\n"; + } + +#endif + } + + if (threshold == 0.0f) { + return (result) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return (len * threshold > error_count) ? true : false; + } +} + +#ifndef __MIN_EPSILON_ERROR +#define __MIN_EPSILON_ERROR 1e-3f +#endif + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference handle to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +//! @param epsilon threshold % of (# of bytes) for pass/fail +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareDataAsFloatThreshold(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + // If we set epsilon to be 0, let's set a minimum threshold + float max_error = MAX((float)epsilon, __MIN_EPSILON_ERROR); + int error_count = 0; + bool result = true; + + for (unsigned int i = 0; i < len; ++i) { + float diff = + fabs(static_cast(reference[i]) - static_cast(data[i])); + bool comp = (diff < max_error); + result &= comp; + + if (!comp) { + error_count++; + } + } + + if (threshold == 0.0f) { + if (error_count) { + printf("total # of errors = %d\n", error_count); + } + + return (error_count == 0) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return ((len * threshold > error_count) ? true : false); + } +} + +inline void sdkDumpBin(void *data, unsigned int bytes, const char *filename) { + printf("sdkDumpBin: <%s>\n", filename); + FILE *fp; + FOPEN(fp, filename, "wb"); + fwrite(data, bytes, 1, fp); + fflush(fp); + fclose(fp); +} + +inline bool sdkCompareBin2BinUint(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + unsigned int *src_buffer, *ref_buffer; + FILE *src_fp = NULL, *ref_fp = NULL; + + uint64_t error_count = 0; + size_t fsize = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", + src_file); + error_count++; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", + ref_file, exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + ref_file); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf( + "compareBin2Bin " + " unable to open ref_file: %s\n", + ref_file_path); + error_count++; + } + + if (src_fp && ref_fp) { + src_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + ref_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + + fsize = fread(src_buffer, nelements, sizeof(unsigned int), src_fp); + fsize = fread(ref_buffer, nelements, sizeof(unsigned int), ref_fp); + + printf( + "> compareBin2Bin nelements=%d," + " epsilon=%4.2f, threshold=%4.2f\n", + nelements, epsilon, threshold); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize)); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize)); + + if (!compareData(ref_buffer, src_buffer, nelements, + epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareBin2BinFloat(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + float *src_buffer = NULL, *ref_buffer = NULL; + FILE *src_fp = NULL, *ref_fp = NULL; + size_t fsize = 0; + + uint64_t error_count = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", src_file); + error_count = 1; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", ref_file, + exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + exec_path); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf("compareBin2Bin unable to open ref_file: %s\n", + ref_file_path); + error_count = 1; + } + + if (src_fp && ref_fp) { + src_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + ref_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + + printf( + "> compareBin2Bin nelements=%d, epsilon=%4.2f," + " threshold=%4.2f\n", + nelements, epsilon, threshold); + fsize = fread(src_buffer, sizeof(float), nelements, src_fp); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize * sizeof(float))); + fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize * sizeof(float))); + + if (!compareDataAsFloatThreshold( + ref_buffer, src_buffer, nelements, epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareL2fe(const float *reference, const float *data, + const unsigned int len, const float epsilon) { + assert(epsilon >= 0); + + float error = 0; + float ref = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = reference[i] - data[i]; + error += diff * diff; + ref += reference[i] * reference[i]; + } + + float normRef = sqrtf(ref); + + if (fabs(ref) < 1e-7) { +#ifdef _DEBUG + std::cerr << "ERROR, reference l2-norm is 0\n"; +#endif + return false; + } + + float normError = sqrtf(error); + error = normError / normRef; + bool result = error < epsilon; +#ifdef _DEBUG + + if (!result) { + std::cerr << "ERROR, l2-norm error " << error << " is greater than epsilon " + << epsilon << "\n"; + } + +#endif + + return result; +} + +inline bool sdkLoadPPMub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned int channels; + return __loadPPM(file, data, w, h, &channels); +} + +inline bool sdkLoadPPM4ub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = (unsigned char *)malloc(sizeof(unsigned char) * size * 4); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool sdkComparePPM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data, *ref_data; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPM4ub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPM4ub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PPMvsPPM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) { + std::cerr << "PPMvsPPM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + } + + if (compareData(ref_data, src_data, src_width * src_height * 4, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +inline bool sdkComparePGM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data = 0, *ref_data = 0; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPMub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPMub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PGMvsPGM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) + std::cerr << "PGMvsPGM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + + if (compareData(ref_data, src_data, src_width * src_height, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +#endif // COMMON_HELPER_IMAGE_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/helper_string.h b/src/cuda/H100/image/dwtHaar1D/helper_string.h new file mode 100644 index 000000000..39a1b3805 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/helper_string.h @@ -0,0 +1,428 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, timers, etc) +#ifndef COMMON_HELPER_STRING_H_ +#define COMMON_HELPER_STRING_H_ + +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#ifndef STRCASECMP +#define STRCASECMP _stricmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP _strnicmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy_s(sFilePath, nLength, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#ifndef SPRINTF +#define SPRINTF sprintf_s +#endif +#else // Linux Includes +#include +#include + +#ifndef STRCASECMP +#define STRCASECMP strcasecmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP strncasecmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy(sFilePath, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#ifndef SPRINTF +#define SPRINTF sprintf +#endif +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// CUDA Utility Helper Functions +inline int stringRemoveDelimiter(char delimiter, const char *string) { + int string_start = 0; + + while (string[string_start] == delimiter) { + string_start++; + } + + if (string_start >= static_cast(strlen(string) - 1)) { + return 0; + } + + return string_start; +} + +inline int getFileExtension(char *filename, char **extension) { + int string_length = static_cast(strlen(filename)); + + while (filename[string_length--] != '.') { + if (string_length == 0) break; + } + + if (string_length > 0) string_length += 2; + + if (string_length == 0) + *extension = NULL; + else + *extension = &filename[string_length]; + + return string_length; +} + +inline bool checkCmdLineFlag(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + + const char *equal_pos = strchr(string_argv, '='); + int argv_length = static_cast( + equal_pos == 0 ? strlen(string_argv) : equal_pos - string_argv); + + int length = static_cast(strlen(string_ref)); + + if (length == argv_length && + !STRNCASECMP(string_argv, string_ref, length)) { + bFound = true; + continue; + } + } + } + + return bFound; +} + +// This function wraps the CUDA Driver API into a template function +template +inline bool getCmdLineArgumentValue(const int argc, const char **argv, + const char *string_ref, T *value) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + *value = (T)atoi(&string_argv[length + auto_inc]); + } + + bFound = true; + i = argc; + } + } + } + + return bFound; +} + +inline int getCmdLineArgumentInt(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + int value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = atoi(&string_argv[length + auto_inc]); + } else { + value = 0; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline float getCmdLineArgumentFloat(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + float value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = static_cast(atof(&string_argv[length + auto_inc])); + } else { + value = 0.f; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline bool getCmdLineArgumentString(const int argc, const char **argv, + const char *string_ref, + char **string_retval) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + char *string_argv = const_cast(&argv[i][string_start]); + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + *string_retval = &string_argv[length + 1]; + bFound = true; + continue; + } + } + } + + if (!bFound) { + *string_retval = NULL; + } + + return bFound; +} + +////////////////////////////////////////////////////////////////////////////// +//! Find the path for a file assuming that +//! files are found in the searchPath. +//! +//! @return the path if succeeded, otherwise 0 +//! @param filename name of the file +//! @param executable_path optional absolute path of the executable +////////////////////////////////////////////////////////////////////////////// +inline char *sdkFindFilePath(const char *filename, + const char *executable_path) { + // defines a variable that is replaced with the name of the + // executable + + // Typical relative search paths to locate needed companion files (e.g. sample + // input data, or JIT source files) The origin for the relative search may be + // the .exe file, a .bat file launching an .exe, a browser .exe launching the + // .exe or .bat, etc + const char *searchPath[] = { + "./", // same dir + "./data/", // same dir + + "../../../../Samples//", // up 4 in tree + "../../../Samples//", // up 3 in tree + "../../Samples//", // up 2 in tree + + "../../../../Samples//data/", // up 4 in tree + "../../../Samples//data/", // up 3 in tree + "../../Samples//data/", // up 2 in tree + + "../../../../Samples/0_Introduction//", // up 4 in tree + "../../../Samples/0_Introduction//", // up 3 in tree + "../../Samples/0_Introduction//", // up 2 in tree + + "../../../../Samples/1_Utilities//", // up 4 in tree + "../../../Samples/1_Utilities//", // up 3 in tree + "../../Samples/1_Utilities//", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//", // up 4 in tree + "../../../Samples/3_CUDA_Features//", // up 3 in tree + "../../Samples/3_CUDA_Features//", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//", // up 3 in tree + "../../Samples/4_CUDA_Libraries//", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//", // up 4 in tree + "../../../Samples/5_Domain_Specific//", // up 3 in tree + "../../Samples/5_Domain_Specific//", // up 2 in tree + + "../../../../Samples/6_Performance//", // up 4 in tree + "../../../Samples/6_Performance//", // up 3 in tree + "../../Samples/6_Performance//", // up 2 in tree + + "../../../../Samples/0_Introduction//data/", // up 4 in tree + "../../../Samples/0_Introduction//data/", // up 3 in tree + "../../Samples/0_Introduction//data/", // up 2 in tree + + "../../../../Samples/1_Utilities//data/", // up 4 in tree + "../../../Samples/1_Utilities//data/", // up 3 in tree + "../../Samples/1_Utilities//data/", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//data/", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//data/", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//data/", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//data/", // up 4 in tree + "../../../Samples/3_CUDA_Features//data/", // up 3 in tree + "../../Samples/3_CUDA_Features//data/", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//data/", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//data/", // up 3 in tree + "../../Samples/4_CUDA_Libraries//data/", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//data/", // up 4 in tree + "../../../Samples/5_Domain_Specific//data/", // up 3 in tree + "../../Samples/5_Domain_Specific//data/", // up 2 in tree + + "../../../../Samples/6_Performance//data/", // up 4 in tree + "../../../Samples/6_Performance//data/", // up 3 in tree + "../../Samples/6_Performance//data/", // up 2 in tree + + "../../../../Common/data/", // up 4 in tree + "../../../Common/data/", // up 3 in tree + "../../Common/data/" // up 2 in tree + }; + + // Extract the executable name + std::string executable_name; + + if (executable_path != 0) { + executable_name = std::string(executable_path); + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + // Windows path delimiter + size_t delimiter_pos = executable_name.find_last_of('\\'); + executable_name.erase(0, delimiter_pos + 1); + + if (executable_name.rfind(".exe") != std::string::npos) { + // we strip .exe, only if the .exe is found + executable_name.resize(executable_name.size() - 4); + } + +#else + // Linux & OSX path delimiter + size_t delimiter_pos = executable_name.find_last_of('/'); + executable_name.erase(0, delimiter_pos + 1); +#endif + } + + // Loop over all search paths and return the first hit + for (unsigned int i = 0; i < sizeof(searchPath) / sizeof(char *); ++i) { + std::string path(searchPath[i]); + size_t executable_name_pos = path.find(""); + + // If there is executable_name variable in the searchPath + // replace it with the value + if (executable_name_pos != std::string::npos) { + if (executable_path != 0) { + path.replace(executable_name_pos, strlen(""), + executable_name); + } else { + // Skip this path entry if no executable argument is given + continue; + } + } + +#ifdef _DEBUG + printf("sdkFindFilePath <%s> in %s\n", filename, path.c_str()); +#endif + + // Test if the file exists + path.append(filename); + FILE *fp; + FOPEN(fp, path.c_str(), "rb"); + + if (fp != NULL) { + fclose(fp); + // File found + // returning an allocated array here for backwards compatibility reasons + char *file_path = reinterpret_cast(malloc(path.length() + 1)); + STRCPY(file_path, path.length() + 1, path.c_str()); + return file_path; + } + + if (fp) { + fclose(fp); + } + } + + // File not found + printf("\nerror: sdkFindFilePath: file <%s> not found!\n", filename); + return 0; +} + +#endif // COMMON_HELPER_STRING_H_ diff --git a/src/cuda/H100/image/dwtHaar1D/helper_timer.h b/src/cuda/H100/image/dwtHaar1D/helper_timer.h new file mode 100644 index 000000000..0614a7802 --- /dev/null +++ b/src/cuda/H100/image/dwtHaar1D/helper_timer.h @@ -0,0 +1,465 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Helper Timing Functions +#ifndef COMMON_HELPER_TIMER_H_ +#define COMMON_HELPER_TIMER_H_ + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// includes, system +#include + +// includes, project +#include "exception.h" + +// Definition of the StopWatch Interface, this is used if we don't want to use +// the CUT functions But rather in a self contained class interface +class StopWatchInterface { + public: + StopWatchInterface() {} + virtual ~StopWatchInterface() {} + + public: + //! Start time measurement + virtual void start() = 0; + + //! Stop time measurement + virtual void stop() = 0; + + //! Reset time counters to zero + virtual void reset() = 0; + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + virtual float getTime() = 0; + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + virtual float getAverageTime() = 0; +}; + +////////////////////////////////////////////////////////////////// +// Begin Stopwatch timer class definitions for all OS platforms // +////////////////////////////////////////////////////////////////// +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +// includes, system +#define WINDOWS_LEAN_AND_MEAN +#include +#undef min +#undef max + +//! Windows specific implementation of StopWatch +class StopWatchWin : public StopWatchInterface { + public: + //! Constructor, default + StopWatchWin() + : start_time(), + end_time(), + diff_time(0.0f), + total_time(0.0f), + running(false), + clock_sessions(0), + freq(0), + freq_set(false) { + if (!freq_set) { + // helper variable + LARGE_INTEGER temp; + + // get the tick frequency from the OS + QueryPerformanceFrequency(reinterpret_cast(&temp)); + + // convert to type in which it is needed + freq = (static_cast(temp.QuadPart)) / 1000.0; + + // rememeber query + freq_set = true; + } + } + + // Destructor + ~StopWatchWin() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // member variables + + //! Start of measurement + LARGE_INTEGER start_time; + //! End of measurement + LARGE_INTEGER end_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; + + //! tick frequency + double freq; + + //! flag if the frequency has been set + bool freq_set; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::start() { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::stop() { + QueryPerformanceCounter(reinterpret_cast(&end_time)); + diff_time = static_cast(((static_cast(end_time.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + + total_time += diff_time; + clock_sessions++; + running = false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + LARGE_INTEGER temp; + QueryPerformanceCounter(reinterpret_cast(&temp)); + retval += static_cast(((static_cast(temp.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +#else +// Declarations for Stopwatch on Linux and Mac OSX +// includes, system +#include +#include + +//! Windows specific implementation of StopWatch +class StopWatchLinux : public StopWatchInterface { + public: + //! Constructor, default + StopWatchLinux() + : start_time(), + diff_time(0.0), + total_time(0.0), + running(false), + clock_sessions(0) {} + + // Destructor + virtual ~StopWatchLinux() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // helper functions + + //! Get difference between start time and current time + inline float getDiffTime(); + + private: + // member variables + + //! Start of measurement + struct timeval start_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::start() { + gettimeofday(&start_time, 0); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::stop() { + diff_time = getDiffTime(); + total_time += diff_time; + running = false; + clock_sessions++; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + gettimeofday(&start_time, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + retval += getDiffTime(); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getDiffTime() { + struct timeval t_time; + gettimeofday(&t_time, 0); + + // time difference in milli-seconds + return static_cast(1000.0 * (t_time.tv_sec - start_time.tv_sec) + + (0.001 * (t_time.tv_usec - start_time.tv_usec))); +} +#endif // WIN32 + +//////////////////////////////////////////////////////////////////////////////// +//! Timer functionality exported + +//////////////////////////////////////////////////////////////////////////////// +//! Create a new timer +//! @return true if a time has been created, otherwise false +//! @param name of the new timer, 0 if the creation failed +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkCreateTimer(StopWatchInterface **timer_interface) { +// printf("sdkCreateTimer called object %08x\n", (void *)*timer_interface); +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + *timer_interface = reinterpret_cast(new StopWatchWin()); +#else + *timer_interface = + reinterpret_cast(new StopWatchLinux()); +#endif + return (*timer_interface != NULL) ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Delete a timer +//! @return true if a time has been deleted, otherwise false +//! @param name of the timer to delete +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkDeleteTimer(StopWatchInterface **timer_interface) { + // printf("sdkDeleteTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + delete *timer_interface; + *timer_interface = NULL; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Start the time with name \a name +//! @param name name of the timer to start +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStartTimer(StopWatchInterface **timer_interface) { + // printf("sdkStartTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->start(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop the time with name \a name. Does not reset. +//! @param name name of the timer to stop +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStopTimer(StopWatchInterface **timer_interface) { + // printf("sdkStopTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->stop(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Resets the timer's counter. +//! @param name name of the timer to reset. +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkResetTimer(StopWatchInterface **timer_interface) { + // printf("sdkResetTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->reset(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Return the average time for timer execution as the total time +//! for the timer dividied by the number of completed (stopped) runs the timer +//! has made. +//! Excludes the current running time if the timer is currently running. +//! @param name name of the timer to return the time of +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetAverageTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetAverageTimerValue called object %08x\n", (void + // *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getAverageTime(); + } else { + return 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Total execution time for the timer over all runs since the last reset +//! or timer creation. +//! @param name name of the timer to obtain the value of. +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetTimerValue called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getTime(); + } else { + return 0.0f; + } +} + +#endif // COMMON_HELPER_TIMER_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/Makefile b/src/cuda/H100/image/recursiveGaussian/Makefile new file mode 100644 index 000000000..362473c4c --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/Makefile @@ -0,0 +1,12 @@ +EXECUTABLE := recursiveGaussian +CUFILES := recursiveGaussian_cuda.cu +CCFILES := recursiveGaussian.cpp +OMIT_CUTIL_LIB := 1 +OMIT_SHRUTIL_LIB := 1 +SM_VERSIONS := 75 80 90 + +# Build in benchmark mode without OpenGL +NVCCFLAGS += -DNO_OPENGL +CXXFLAGS += -DNO_OPENGL + +include ../../../common/common.mk diff --git a/src/cuda/H100/image/recursiveGaussian/exception.h b/src/cuda/H100/image/recursiveGaussian/exception.h new file mode 100644 index 000000000..ca8ac2525 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/exception.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* CUda UTility Library */ +#ifndef COMMON_EXCEPTION_H_ +#define COMMON_EXCEPTION_H_ + +// includes, system +#include +#include +#include +#include +#include + +//! Exception wrapper. +//! @param Std_Exception Exception out of namespace std for easy typing. +template +class Exception : public Std_Exception { + public: + //! @brief Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const char *detailed = "-"); + + //! Static construction interface + //! @return Alwayss throws ( Located_Exception) + //! @param file file in which the Exception occurs + //! @param line line in which the Exception occurs + //! @param detailed details on the code fragment causing the Exception + static void throw_it(const char *file, const int line, + const std::string &detailed); + + //! Destructor + virtual ~Exception() throw(); + + private: + //! Constructor, default (private) + Exception(); + + //! Constructor, standard + //! @param str string returned by what() + explicit Exception(const std::string &str); +}; + +//////////////////////////////////////////////////////////////////////////////// +//! Exception handler function for arbitrary exceptions +//! @param ex exception to handle +//////////////////////////////////////////////////////////////////////////////// +template +inline void handleException(const Exception_Typ &ex) { + std::cerr << ex.what() << std::endl; + + exit(EXIT_FAILURE); +} + +//! Convenience macros + +//! Exception caused by dynamic program behavior, e.g. file does not exist +#define RUNTIME_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Logic exception in program, e.g. an assert failed +#define LOGIC_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//! Out of range exception +#define RANGE_EXCEPTION(msg) \ + Exception::throw_it(__FILE__, __LINE__, msg) + +//////////////////////////////////////////////////////////////////////////////// +//! Implementation + +// includes, system +#include + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const char *detailed) { + std::stringstream s; + + // Quiet heavy-weight but exceptions are not for + // performance / release versions + s << "Exception in file '" << file << "' in line " << line << "\n" + << "Detailed description: " << detailed << "\n"; + + throw Exception(s.str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Static construction interface. +//! @param Exception causing code fragment (file and line) and detailed infos. +//////////////////////////////////////////////////////////////////////////////// +/*static*/ template +void Exception::throw_it(const char *file, const int line, + const std::string &msg) { + throw_it(file, line, msg.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, default (private). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception() : Std_Exception("Unknown Exception.\n") {} + +//////////////////////////////////////////////////////////////////////////////// +//! Constructor, standard (private). +//! String returned by what(). +//////////////////////////////////////////////////////////////////////////////// +template +Exception::Exception(const std::string &s) : Std_Exception(s) {} + +//////////////////////////////////////////////////////////////////////////////// +//! Destructor +//////////////////////////////////////////////////////////////////////////////// +template +Exception::~Exception() throw() {} + + // functions, exported + +#endif // COMMON_EXCEPTION_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_cuda.h b/src/cuda/H100/image/recursiveGaussian/helper_cuda.h new file mode 100644 index 000000000..723b28f7a --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_cuda.h @@ -0,0 +1,988 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//////////////////////////////////////////////////////////////////////////////// +// These are CUDA Helper functions for initialization and error checking + +#ifndef COMMON_HELPER_CUDA_H_ +#define COMMON_HELPER_CUDA_H_ + +#pragma once + +#include +#include +#include +#include + +#include "helper_string.h" + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// Note, it is required that your SDK sample to include the proper header +// files, please refer the CUDA examples for examples of the needed CUDA +// headers, which may change depending on which CUDA functions are used. + +// CUDA Runtime error messages +#ifdef __DRIVER_TYPES_H__ +static const char *_cudaGetErrorEnum(cudaError_t error) { + return cudaGetErrorName(error); +} +#endif + +#ifdef CUDA_DRIVER_API +// CUDA Driver API errors +static const char *_cudaGetErrorEnum(CUresult error) { + static char unknown[] = ""; + const char *ret = NULL; + cuGetErrorName(error, &ret); + return ret ? ret : unknown; +} +#endif + +#ifdef CUBLAS_API_H_ +// cuBLAS API errors +static const char *_cudaGetErrorEnum(cublasStatus_t error) { + switch (error) { + case CUBLAS_STATUS_SUCCESS: + return "CUBLAS_STATUS_SUCCESS"; + + case CUBLAS_STATUS_NOT_INITIALIZED: + return "CUBLAS_STATUS_NOT_INITIALIZED"; + + case CUBLAS_STATUS_ALLOC_FAILED: + return "CUBLAS_STATUS_ALLOC_FAILED"; + + case CUBLAS_STATUS_INVALID_VALUE: + return "CUBLAS_STATUS_INVALID_VALUE"; + + case CUBLAS_STATUS_ARCH_MISMATCH: + return "CUBLAS_STATUS_ARCH_MISMATCH"; + + case CUBLAS_STATUS_MAPPING_ERROR: + return "CUBLAS_STATUS_MAPPING_ERROR"; + + case CUBLAS_STATUS_EXECUTION_FAILED: + return "CUBLAS_STATUS_EXECUTION_FAILED"; + + case CUBLAS_STATUS_INTERNAL_ERROR: + return "CUBLAS_STATUS_INTERNAL_ERROR"; + + case CUBLAS_STATUS_NOT_SUPPORTED: + return "CUBLAS_STATUS_NOT_SUPPORTED"; + + case CUBLAS_STATUS_LICENSE_ERROR: + return "CUBLAS_STATUS_LICENSE_ERROR"; + } + + return ""; +} +#endif + +#ifdef _CUFFT_H_ +// cuFFT API errors +static const char *_cudaGetErrorEnum(cufftResult error) { + switch (error) { + case CUFFT_SUCCESS: + return "CUFFT_SUCCESS"; + + case CUFFT_INVALID_PLAN: + return "CUFFT_INVALID_PLAN"; + + case CUFFT_ALLOC_FAILED: + return "CUFFT_ALLOC_FAILED"; + + case CUFFT_INVALID_TYPE: + return "CUFFT_INVALID_TYPE"; + + case CUFFT_INVALID_VALUE: + return "CUFFT_INVALID_VALUE"; + + case CUFFT_INTERNAL_ERROR: + return "CUFFT_INTERNAL_ERROR"; + + case CUFFT_EXEC_FAILED: + return "CUFFT_EXEC_FAILED"; + + case CUFFT_SETUP_FAILED: + return "CUFFT_SETUP_FAILED"; + + case CUFFT_INVALID_SIZE: + return "CUFFT_INVALID_SIZE"; + + case CUFFT_UNALIGNED_DATA: + return "CUFFT_UNALIGNED_DATA"; + + case CUFFT_INVALID_DEVICE: + return "CUFFT_INVALID_DEVICE"; + + case CUFFT_NO_WORKSPACE: + return "CUFFT_NO_WORKSPACE"; + + case CUFFT_NOT_IMPLEMENTED: + return "CUFFT_NOT_IMPLEMENTED"; + + case CUFFT_NOT_SUPPORTED: + return "CUFFT_NOT_SUPPORTED"; + + case CUFFT_MISSING_DEPENDENCY: + return "CUFFT_MISSING_DEPENDENCY"; + + case CUFFT_NVRTC_FAILURE: + return "CUFFT_NVRTC_FAILURE"; + + case CUFFT_NVJITLINK_FAILURE: + return "CUFFT_NVJITLINK_FAILURE"; + + case CUFFT_NVSHMEM_FAILURE: + return "CUFFT_NVSHMEM_FAILURE"; + } + + return ""; +} +#endif + +#ifdef CUSPARSEAPI +// cuSPARSE API errors +static const char *_cudaGetErrorEnum(cusparseStatus_t error) { + switch (error) { + case CUSPARSE_STATUS_SUCCESS: + return "CUSPARSE_STATUS_SUCCESS"; + + case CUSPARSE_STATUS_NOT_INITIALIZED: + return "CUSPARSE_STATUS_NOT_INITIALIZED"; + + case CUSPARSE_STATUS_ALLOC_FAILED: + return "CUSPARSE_STATUS_ALLOC_FAILED"; + + case CUSPARSE_STATUS_INVALID_VALUE: + return "CUSPARSE_STATUS_INVALID_VALUE"; + + case CUSPARSE_STATUS_ARCH_MISMATCH: + return "CUSPARSE_STATUS_ARCH_MISMATCH"; + + case CUSPARSE_STATUS_MAPPING_ERROR: + return "CUSPARSE_STATUS_MAPPING_ERROR"; + + case CUSPARSE_STATUS_EXECUTION_FAILED: + return "CUSPARSE_STATUS_EXECUTION_FAILED"; + + case CUSPARSE_STATUS_INTERNAL_ERROR: + return "CUSPARSE_STATUS_INTERNAL_ERROR"; + + case CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + } + + return ""; +} +#endif + +#ifdef CUSOLVER_COMMON_H_ +// cuSOLVER API errors +static const char *_cudaGetErrorEnum(cusolverStatus_t error) { + switch (error) { + case CUSOLVER_STATUS_SUCCESS: + return "CUSOLVER_STATUS_SUCCESS"; + case CUSOLVER_STATUS_NOT_INITIALIZED: + return "CUSOLVER_STATUS_NOT_INITIALIZED"; + case CUSOLVER_STATUS_ALLOC_FAILED: + return "CUSOLVER_STATUS_ALLOC_FAILED"; + case CUSOLVER_STATUS_INVALID_VALUE: + return "CUSOLVER_STATUS_INVALID_VALUE"; + case CUSOLVER_STATUS_ARCH_MISMATCH: + return "CUSOLVER_STATUS_ARCH_MISMATCH"; + case CUSOLVER_STATUS_MAPPING_ERROR: + return "CUSOLVER_STATUS_MAPPING_ERROR"; + case CUSOLVER_STATUS_EXECUTION_FAILED: + return "CUSOLVER_STATUS_EXECUTION_FAILED"; + case CUSOLVER_STATUS_INTERNAL_ERROR: + return "CUSOLVER_STATUS_INTERNAL_ERROR"; + case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED: + return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED"; + case CUSOLVER_STATUS_NOT_SUPPORTED: + return "CUSOLVER_STATUS_NOT_SUPPORTED "; + case CUSOLVER_STATUS_ZERO_PIVOT: + return "CUSOLVER_STATUS_ZERO_PIVOT"; + case CUSOLVER_STATUS_INVALID_LICENSE: + return "CUSOLVER_STATUS_INVALID_LICENSE"; + } + + return ""; +} +#endif + +#ifdef CURAND_H_ +// cuRAND API errors +static const char *_cudaGetErrorEnum(curandStatus_t error) { + switch (error) { + case CURAND_STATUS_SUCCESS: + return "CURAND_STATUS_SUCCESS"; + + case CURAND_STATUS_VERSION_MISMATCH: + return "CURAND_STATUS_VERSION_MISMATCH"; + + case CURAND_STATUS_NOT_INITIALIZED: + return "CURAND_STATUS_NOT_INITIALIZED"; + + case CURAND_STATUS_ALLOCATION_FAILED: + return "CURAND_STATUS_ALLOCATION_FAILED"; + + case CURAND_STATUS_TYPE_ERROR: + return "CURAND_STATUS_TYPE_ERROR"; + + case CURAND_STATUS_OUT_OF_RANGE: + return "CURAND_STATUS_OUT_OF_RANGE"; + + case CURAND_STATUS_LENGTH_NOT_MULTIPLE: + return "CURAND_STATUS_LENGTH_NOT_MULTIPLE"; + + case CURAND_STATUS_DOUBLE_PRECISION_REQUIRED: + return "CURAND_STATUS_DOUBLE_PRECISION_REQUIRED"; + + case CURAND_STATUS_LAUNCH_FAILURE: + return "CURAND_STATUS_LAUNCH_FAILURE"; + + case CURAND_STATUS_PREEXISTING_FAILURE: + return "CURAND_STATUS_PREEXISTING_FAILURE"; + + case CURAND_STATUS_INITIALIZATION_FAILED: + return "CURAND_STATUS_INITIALIZATION_FAILED"; + + case CURAND_STATUS_ARCH_MISMATCH: + return "CURAND_STATUS_ARCH_MISMATCH"; + + case CURAND_STATUS_INTERNAL_ERROR: + return "CURAND_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NVJPEGAPI +// nvJPEG API errors +static const char *_cudaGetErrorEnum(nvjpegStatus_t error) { + switch (error) { + case NVJPEG_STATUS_SUCCESS: + return "NVJPEG_STATUS_SUCCESS"; + + case NVJPEG_STATUS_NOT_INITIALIZED: + return "NVJPEG_STATUS_NOT_INITIALIZED"; + + case NVJPEG_STATUS_INVALID_PARAMETER: + return "NVJPEG_STATUS_INVALID_PARAMETER"; + + case NVJPEG_STATUS_BAD_JPEG: + return "NVJPEG_STATUS_BAD_JPEG"; + + case NVJPEG_STATUS_JPEG_NOT_SUPPORTED: + return "NVJPEG_STATUS_JPEG_NOT_SUPPORTED"; + + case NVJPEG_STATUS_ALLOCATOR_FAILURE: + return "NVJPEG_STATUS_ALLOCATOR_FAILURE"; + + case NVJPEG_STATUS_EXECUTION_FAILED: + return "NVJPEG_STATUS_EXECUTION_FAILED"; + + case NVJPEG_STATUS_ARCH_MISMATCH: + return "NVJPEG_STATUS_ARCH_MISMATCH"; + + case NVJPEG_STATUS_INTERNAL_ERROR: + return "NVJPEG_STATUS_INTERNAL_ERROR"; + } + + return ""; +} +#endif + +#ifdef NV_NPPIDEFS_H +// NPP API errors +static const char *_cudaGetErrorEnum(NppStatus error) { + switch (error) { + case NPP_NOT_SUPPORTED_MODE_ERROR: + return "NPP_NOT_SUPPORTED_MODE_ERROR"; + + case NPP_ROUND_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ROUND_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_RESIZE_NO_OPERATION_ERROR: + return "NPP_RESIZE_NO_OPERATION_ERROR"; + + case NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY: + return "NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_BAD_ARG_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFF_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECT_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUAD_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEM_ALLOC_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTO_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_INPUT: + return "NPP_INVALID_INPUT"; + + case NPP_POINTER_ERROR: + return "NPP_POINTER_ERROR"; + + case NPP_WARNING: + return "NPP_WARNING"; + + case NPP_ODD_ROI_WARNING: + return "NPP_ODD_ROI_WARNING"; +#else + + // These are for CUDA 5.5 or higher + case NPP_BAD_ARGUMENT_ERROR: + return "NPP_BAD_ARGUMENT_ERROR"; + + case NPP_COEFFICIENT_ERROR: + return "NPP_COEFFICIENT_ERROR"; + + case NPP_RECTANGLE_ERROR: + return "NPP_RECTANGLE_ERROR"; + + case NPP_QUADRANGLE_ERROR: + return "NPP_QUADRANGLE_ERROR"; + + case NPP_MEMORY_ALLOCATION_ERR: + return "NPP_MEMORY_ALLOCATION_ERROR"; + + case NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR: + return "NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR"; + + case NPP_INVALID_HOST_POINTER_ERROR: + return "NPP_INVALID_HOST_POINTER_ERROR"; + + case NPP_INVALID_DEVICE_POINTER_ERROR: + return "NPP_INVALID_DEVICE_POINTER_ERROR"; +#endif + + case NPP_LUT_NUMBER_OF_LEVELS_ERROR: + return "NPP_LUT_NUMBER_OF_LEVELS_ERROR"; + + case NPP_TEXTURE_BIND_ERROR: + return "NPP_TEXTURE_BIND_ERROR"; + + case NPP_WRONG_INTERSECTION_ROI_ERROR: + return "NPP_WRONG_INTERSECTION_ROI_ERROR"; + + case NPP_NOT_EVEN_STEP_ERROR: + return "NPP_NOT_EVEN_STEP_ERROR"; + + case NPP_INTERPOLATION_ERROR: + return "NPP_INTERPOLATION_ERROR"; + + case NPP_RESIZE_FACTOR_ERROR: + return "NPP_RESIZE_FACTOR_ERROR"; + + case NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR: + return "NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) <= 0x5000 + + case NPP_MEMFREE_ERR: + return "NPP_MEMFREE_ERR"; + + case NPP_MEMSET_ERR: + return "NPP_MEMSET_ERR"; + + case NPP_MEMCPY_ERR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERR: + return "NPP_MIRROR_FLIP_ERR"; +#else + + case NPP_MEMFREE_ERROR: + return "NPP_MEMFREE_ERROR"; + + case NPP_MEMSET_ERROR: + return "NPP_MEMSET_ERROR"; + + case NPP_MEMCPY_ERROR: + return "NPP_MEMCPY_ERROR"; + + case NPP_MIRROR_FLIP_ERROR: + return "NPP_MIRROR_FLIP_ERROR"; +#endif + + case NPP_ALIGNMENT_ERROR: + return "NPP_ALIGNMENT_ERROR"; + + case NPP_STEP_ERROR: + return "NPP_STEP_ERROR"; + + case NPP_SIZE_ERROR: + return "NPP_SIZE_ERROR"; + + case NPP_NULL_POINTER_ERROR: + return "NPP_NULL_POINTER_ERROR"; + + case NPP_CUDA_KERNEL_EXECUTION_ERROR: + return "NPP_CUDA_KERNEL_EXECUTION_ERROR"; + + case NPP_NOT_IMPLEMENTED_ERROR: + return "NPP_NOT_IMPLEMENTED_ERROR"; + + case NPP_ERROR: + return "NPP_ERROR"; + + case NPP_SUCCESS: + return "NPP_SUCCESS"; + + case NPP_WRONG_INTERSECTION_QUAD_WARNING: + return "NPP_WRONG_INTERSECTION_QUAD_WARNING"; + + case NPP_MISALIGNED_DST_ROI_WARNING: + return "NPP_MISALIGNED_DST_ROI_WARNING"; + + case NPP_AFFINE_QUAD_INCORRECT_WARNING: + return "NPP_AFFINE_QUAD_INCORRECT_WARNING"; + + case NPP_DOUBLE_SIZE_WARNING: + return "NPP_DOUBLE_SIZE_WARNING"; + + case NPP_WRONG_INTERSECTION_ROI_WARNING: + return "NPP_WRONG_INTERSECTION_ROI_WARNING"; + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x6000 + /* These are 6.0 or higher */ + case NPP_LUT_PALETTE_BITSIZE_ERROR: + return "NPP_LUT_PALETTE_BITSIZE_ERROR"; + + case NPP_ZC_MODE_NOT_SUPPORTED_ERROR: + return "NPP_ZC_MODE_NOT_SUPPORTED_ERROR"; + + case NPP_QUALITY_INDEX_ERROR: + return "NPP_QUALITY_INDEX_ERROR"; + + case NPP_CHANNEL_ORDER_ERROR: + return "NPP_CHANNEL_ORDER_ERROR"; + + case NPP_ZERO_MASK_VALUE_ERROR: + return "NPP_ZERO_MASK_VALUE_ERROR"; + + case NPP_NUMBER_OF_CHANNELS_ERROR: + return "NPP_NUMBER_OF_CHANNELS_ERROR"; + + case NPP_COI_ERROR: + return "NPP_COI_ERROR"; + + case NPP_DIVISOR_ERROR: + return "NPP_DIVISOR_ERROR"; + + case NPP_CHANNEL_ERROR: + return "NPP_CHANNEL_ERROR"; + + case NPP_STRIDE_ERROR: + return "NPP_STRIDE_ERROR"; + + case NPP_ANCHOR_ERROR: + return "NPP_ANCHOR_ERROR"; + + case NPP_MASK_SIZE_ERROR: + return "NPP_MASK_SIZE_ERROR"; + + case NPP_MOMENT_00_ZERO_ERROR: + return "NPP_MOMENT_00_ZERO_ERROR"; + + case NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR: + return "NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR"; + + case NPP_THRESHOLD_ERROR: + return "NPP_THRESHOLD_ERROR"; + + case NPP_CONTEXT_MATCH_ERROR: + return "NPP_CONTEXT_MATCH_ERROR"; + + case NPP_FFT_FLAG_ERROR: + return "NPP_FFT_FLAG_ERROR"; + + case NPP_FFT_ORDER_ERROR: + return "NPP_FFT_ORDER_ERROR"; + + case NPP_SCALE_RANGE_ERROR: + return "NPP_SCALE_RANGE_ERROR"; + + case NPP_DATA_TYPE_ERROR: + return "NPP_DATA_TYPE_ERROR"; + + case NPP_OUT_OFF_RANGE_ERROR: + return "NPP_OUT_OFF_RANGE_ERROR"; + + case NPP_DIVIDE_BY_ZERO_ERROR: + return "NPP_DIVIDE_BY_ZERO_ERROR"; + + case NPP_RANGE_ERROR: + return "NPP_RANGE_ERROR"; + + case NPP_NO_MEMORY_ERROR: + return "NPP_NO_MEMORY_ERROR"; + + case NPP_ERROR_RESERVED: + return "NPP_ERROR_RESERVED"; + + case NPP_NO_OPERATION_WARNING: + return "NPP_NO_OPERATION_WARNING"; + + case NPP_DIVIDE_BY_ZERO_WARNING: + return "NPP_DIVIDE_BY_ZERO_WARNING"; +#endif + +#if ((NPP_VERSION_MAJOR << 12) + (NPP_VERSION_MINOR << 4)) >= 0x7000 + /* These are 7.0 or higher */ + case NPP_OVERFLOW_ERROR: + return "NPP_OVERFLOW_ERROR"; + + case NPP_CORRUPTED_DATA_ERROR: + return "NPP_CORRUPTED_DATA_ERROR"; +#endif + } + + return ""; +} +#endif + +template +void check(T result, char const *const func, const char *const file, + int const line) { + if (result) { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \"%s\" \n", file, line, + static_cast(result), _cudaGetErrorEnum(result), func); + exit(EXIT_FAILURE); + } +} + +#ifdef __DRIVER_TYPES_H__ +// This will output the proper CUDA error strings in the event +// that a CUDA host call returns an error +#define checkCudaErrors(val) check((val), #val, __FILE__, __LINE__) + +// This will output the proper error string when calling cudaGetLastError +#define getLastCudaError(msg) __getLastCudaError(msg, __FILE__, __LINE__) + +inline void __getLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + exit(EXIT_FAILURE); + } +} + +// This will only print the proper error string when calling cudaGetLastError +// but not exit program incase error detected. +#define printLastCudaError(msg) __printLastCudaError(msg, __FILE__, __LINE__) + +inline void __printLastCudaError(const char *errorMessage, const char *file, + const int line) { + cudaError_t err = cudaGetLastError(); + + if (cudaSuccess != err) { + fprintf(stderr, + "%s(%i) : getLastCudaError() CUDA error :" + " %s : (%d) %s.\n", + file, line, errorMessage, static_cast(err), + cudaGetErrorString(err)); + } +} +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif + +// Float To Int conversion +inline int ftoi(float value) { + return (value >= 0 ? static_cast(value + 0.5) + : static_cast(value - 0.5)); +} + +// Beginning of GPU Architecture definitions +inline int _ConvertSMVer2Cores(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the # of cores per SM + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + int Cores; + } sSMtoCores; + + sSMtoCores nGpuArchCoresPerSM[] = { + {0x30, 192}, + {0x32, 192}, + {0x35, 192}, + {0x37, 192}, + {0x50, 128}, + {0x52, 128}, + {0x53, 128}, + {0x60, 64}, + {0x61, 128}, + {0x62, 128}, + {0x70, 64}, + {0x72, 64}, + {0x75, 64}, + {0x80, 64}, + {0x86, 128}, + {0x87, 128}, + {0x89, 128}, + {0x90, 128}, + {0xa0, 128}, + {0xa1, 128}, + {0xa3, 128}, + {0xb0, 128}, + {0xc0, 128}, + {0xc1, 128}, + {-1, -1}}; + + int index = 0; + + while (nGpuArchCoresPerSM[index].SM != -1) { + if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)) { + return nGpuArchCoresPerSM[index].Cores; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoCores for SM %d.%d is undefined." + " Default to use %d Cores/SM\n", + major, minor, nGpuArchCoresPerSM[index - 1].Cores); + return nGpuArchCoresPerSM[index - 1].Cores; +} + +inline const char* _ConvertSMVer2ArchName(int major, int minor) { + // Defines for GPU Architecture types (using the SM version to determine + // the GPU Arch name) + typedef struct { + int SM; // 0xMm (hexidecimal notation), M = SM Major version, + // and m = SM minor version + const char* name; + } sSMtoArchName; + + sSMtoArchName nGpuArchNameSM[] = { + {0x30, "Kepler"}, + {0x32, "Kepler"}, + {0x35, "Kepler"}, + {0x37, "Kepler"}, + {0x50, "Maxwell"}, + {0x52, "Maxwell"}, + {0x53, "Maxwell"}, + {0x60, "Pascal"}, + {0x61, "Pascal"}, + {0x62, "Pascal"}, + {0x70, "Volta"}, + {0x72, "Xavier"}, + {0x75, "Turing"}, + {0x80, "Ampere"}, + {0x86, "Ampere"}, + {0x87, "Ampere"}, + {0x89, "Ada"}, + {0x90, "Hopper"}, + {0xa0, "Blackwell"}, + {0xa1, "Blackwell"}, + {0xa3, "Blackwell"}, + {0xb0, "Blackwell"}, + {0xc0, "Blackwell"}, + {0xc1, "Blackwell"}, + {-1, "Graphics Device"}}; + + int index = 0; + + while (nGpuArchNameSM[index].SM != -1) { + if (nGpuArchNameSM[index].SM == ((major << 4) + minor)) { + return nGpuArchNameSM[index].name; + } + + index++; + } + + // If we don't find the values, we default use the previous one + // to run properly + printf( + "MapSMtoArchName for SM %d.%d is undefined." + " Default to use %s\n", + major, minor, nGpuArchNameSM[index - 1].name); + return nGpuArchNameSM[index - 1].name; +} + // end of GPU Architecture definitions + +#ifdef __CUDA_RUNTIME_H__ +// General GPU Device CUDA Initialization +inline int gpuDeviceInit(int devID) { + int device_count; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuDeviceInit() CUDA error: " + "no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + if (devID < 0) { + devID = 0; + } + + if (devID > device_count - 1) { + fprintf(stderr, "\n"); + fprintf(stderr, ">> %d CUDA capable GPU device(s) detected. <<\n", + device_count); + fprintf(stderr, + ">> gpuDeviceInit (-device=%d) is not a valid" + " GPU device. <<\n", + devID); + fprintf(stderr, "\n"); + return -devID; + } + + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + if (computeMode == cudaComputeModeProhibited) { + fprintf(stderr, + "Error: device is running in , no threads can use cudaSetDevice().\n"); + return -1; + } + + if (major < 1) { + fprintf(stderr, "gpuDeviceInit(): GPU device does not support CUDA.\n"); + exit(EXIT_FAILURE); + } + + checkCudaErrors(cudaSetDevice(devID)); + printf("gpuDeviceInit() CUDA Device [%d]: \"%s\n", devID, _ConvertSMVer2ArchName(major, minor)); + + return devID; +} + +// This function returns the best GPU (with maximum GFLOPS) +inline int gpuGetMaxGflopsDeviceId() { + int current_device = 0, sm_per_multiproc = 0; + int max_perf_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + uint64_t max_compute_perf = 0; + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the best CUDA capable GPU device + current_device = 0; + + while (current_device < device_count) { + int computeMode = -1, major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + + // If this GPU is not running on Compute Mode prohibited, + // then we can add it to the list + if (computeMode != cudaComputeModeProhibited) { + if (major == 9999 && minor == 9999) { + sm_per_multiproc = 1; + } else { + sm_per_multiproc = + _ConvertSMVer2Cores(major, minor); + } + int multiProcessorCount = 0, clockRate = 0; + checkCudaErrors(cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, current_device)); + cudaError_t result = cudaDeviceGetAttribute(&clockRate, cudaDevAttrClockRate, current_device); + if (result != cudaSuccess) { + // If cudaDevAttrClockRate attribute is not supported we + // set clockRate as 1, to consider GPU with most SMs and CUDA Cores. + if(result == cudaErrorInvalidValue) { + clockRate = 1; + } + else { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \n", __FILE__, __LINE__, + static_cast(result), _cudaGetErrorEnum(result)); + exit(EXIT_FAILURE); + } + } + uint64_t compute_perf = (uint64_t)multiProcessorCount * sm_per_multiproc * clockRate; + + if (compute_perf > max_compute_perf) { + max_compute_perf = compute_perf; + max_perf_device = current_device; + } + } else { + devices_prohibited++; + } + + ++current_device; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "gpuGetMaxGflopsDeviceId() CUDA error:" + " all devices have compute mode prohibited.\n"); + exit(EXIT_FAILURE); + } + + return max_perf_device; +} + +// Initialization code to find the best CUDA Device +inline int findCudaDevice(int argc, const char **argv) { + int devID = 0; + + // If the command-line has a device number specified, use it + if (checkCmdLineFlag(argc, argv, "device")) { + devID = getCmdLineArgumentInt(argc, argv, "device="); + + if (devID < 0) { + printf("Invalid command line parameter\n "); + exit(EXIT_FAILURE); + } else { + devID = gpuDeviceInit(devID); + + if (devID < 0) { + printf("exiting...\n"); + exit(EXIT_FAILURE); + } + } + } else { + // Otherwise pick the device with highest Gflops/s + devID = gpuGetMaxGflopsDeviceId(); + checkCudaErrors(cudaSetDevice(devID)); + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + devID, _ConvertSMVer2ArchName(major, minor), major, minor); + + } + + return devID; +} + +inline int findIntegratedGPU() { + int current_device = 0; + int device_count = 0; + int devices_prohibited = 0; + + checkCudaErrors(cudaGetDeviceCount(&device_count)); + + if (device_count == 0) { + fprintf(stderr, "CUDA error: no devices supporting CUDA.\n"); + exit(EXIT_FAILURE); + } + + // Find the integrated GPU which is compute capable + while (current_device < device_count) { + int computeMode = -1, integrated = -1; + checkCudaErrors(cudaDeviceGetAttribute(&computeMode, cudaDevAttrComputeMode, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&integrated, cudaDevAttrIntegrated, current_device)); + // If GPU is integrated and is not running on Compute Mode prohibited, + // then cuda can map to GLES resource + if (integrated && (computeMode != cudaComputeModeProhibited)) { + checkCudaErrors(cudaSetDevice(current_device)); + + int major = 0, minor = 0; + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, current_device)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, current_device)); + printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", + current_device, _ConvertSMVer2ArchName(major, minor), major, minor); + + return current_device; + } else { + devices_prohibited++; + } + + current_device++; + } + + if (devices_prohibited == device_count) { + fprintf(stderr, + "CUDA error:" + " No GLES-CUDA Interop capable GPU found.\n"); + exit(EXIT_FAILURE); + } + + return -1; +} + +// General check for CUDA GPU SM Capabilities +inline bool checkCudaCapabilities(int major_version, int minor_version) { + int dev; + int major = 0, minor = 0; + + checkCudaErrors(cudaGetDevice(&dev)); + checkCudaErrors(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, dev)); + checkCudaErrors(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, dev)); + + if ((major > major_version) || + (major == major_version && + minor >= minor_version)) { + printf(" Device %d: <%16s >, Compute SM %d.%d detected\n", dev, + _ConvertSMVer2ArchName(major, minor), major, minor); + return true; + } else { + printf( + " No GPU device was found that can support " + "CUDA compute capability %d.%d.\n", + major_version, minor_version); + return false; + } +} +#endif + + // end of CUDA Helper Functions + +#endif // COMMON_HELPER_CUDA_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_functions.h b/src/cuda/H100/image/recursiveGaussian/helper_functions.h new file mode 100644 index 000000000..f71da5761 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_functions.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, +// timers, image helpers, etc) +#ifndef COMMON_HELPER_FUNCTIONS_H_ +#define COMMON_HELPER_FUNCTIONS_H_ + +#ifdef WIN32 +#pragma warning(disable : 4996) +#endif + +// includes, project +#include +#include "exception.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +// includes, timer, string parsing, image helpers +#include "helper_image.h" // helper functions for image compare, dump, data comparisons +#include "helper_string.h" // helper functions for string parsing +#include "helper_timer.h" // helper functions for timers + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#endif // COMMON_HELPER_FUNCTIONS_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_gl.h b/src/cuda/H100/image/recursiveGaussian/helper_gl.h new file mode 100644 index 000000000..85c075595 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_gl.h @@ -0,0 +1,267 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (OpenGL) +#ifndef HELPER_GL_H +#define HELPER_GL_H + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + #include +#endif + +#if defined(__APPLE__) || defined(MACOSX) + #include +#else + #include + #ifdef __linux__ + #include + #endif /* __linux__ */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Prototypes */ +namespace __HelperGL { + static int isGLVersionSupported(unsigned reqMajor, unsigned reqMinor); + static int areGLExtensionsSupported(const std::string &); +#ifdef __linux__ + + #ifndef HELPERGL_EXTERN_GL_FUNC_IMPLEMENTATION + #define USE_GL_FUNC(name, proto) proto name = (proto) glXGetProcAddress ((const GLubyte *)#name) + #else + #define USE_GL_FUNC(name, proto) extern proto name + #endif + + USE_GL_FUNC(glBindBuffer, PFNGLBINDBUFFERPROC); + USE_GL_FUNC(glDeleteBuffers, PFNGLDELETEBUFFERSPROC); + USE_GL_FUNC(glBufferData, PFNGLBUFFERDATAPROC); + USE_GL_FUNC(glBufferSubData, PFNGLBUFFERSUBDATAPROC); + USE_GL_FUNC(glGenBuffers, PFNGLGENBUFFERSPROC); + USE_GL_FUNC(glCreateProgram, PFNGLCREATEPROGRAMPROC); + USE_GL_FUNC(glBindProgramARB, PFNGLBINDPROGRAMARBPROC); + USE_GL_FUNC(glGenProgramsARB, PFNGLGENPROGRAMSARBPROC); + USE_GL_FUNC(glDeleteProgramsARB, PFNGLDELETEPROGRAMSARBPROC); + USE_GL_FUNC(glDeleteProgram, PFNGLDELETEPROGRAMPROC); + USE_GL_FUNC(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC); + USE_GL_FUNC(glGetProgramiv, PFNGLGETPROGRAMIVPROC); + USE_GL_FUNC(glProgramParameteriEXT, PFNGLPROGRAMPARAMETERIEXTPROC); + USE_GL_FUNC(glProgramStringARB, PFNGLPROGRAMSTRINGARBPROC); + USE_GL_FUNC(glUnmapBuffer, PFNGLUNMAPBUFFERPROC); + USE_GL_FUNC(glMapBuffer, PFNGLMAPBUFFERPROC); + USE_GL_FUNC(glGetBufferParameteriv, PFNGLGETBUFFERPARAMETERIVPROC); + USE_GL_FUNC(glLinkProgram, PFNGLLINKPROGRAMPROC); + USE_GL_FUNC(glUseProgram, PFNGLUSEPROGRAMPROC); + USE_GL_FUNC(glAttachShader, PFNGLATTACHSHADERPROC); + USE_GL_FUNC(glCreateShader, PFNGLCREATESHADERPROC); + USE_GL_FUNC(glShaderSource, PFNGLSHADERSOURCEPROC); + USE_GL_FUNC(glCompileShader, PFNGLCOMPILESHADERPROC); + USE_GL_FUNC(glDeleteShader, PFNGLDELETESHADERPROC); + USE_GL_FUNC(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC); + USE_GL_FUNC(glGetShaderiv, PFNGLGETSHADERIVPROC); + USE_GL_FUNC(glUniform1i, PFNGLUNIFORM1IPROC); + USE_GL_FUNC(glUniform1f, PFNGLUNIFORM1FPROC); + USE_GL_FUNC(glUniform2f, PFNGLUNIFORM2FPROC); + USE_GL_FUNC(glUniform3f, PFNGLUNIFORM3FPROC); + USE_GL_FUNC(glUniform4f, PFNGLUNIFORM4FPROC); + USE_GL_FUNC(glUniform1fv, PFNGLUNIFORM1FVPROC); + USE_GL_FUNC(glUniform2fv, PFNGLUNIFORM2FVPROC); + USE_GL_FUNC(glUniform3fv, PFNGLUNIFORM3FVPROC); + USE_GL_FUNC(glUniform4fv, PFNGLUNIFORM4FVPROC); + USE_GL_FUNC(glUniformMatrix4fv, PFNGLUNIFORMMATRIX4FVPROC); + USE_GL_FUNC(glSecondaryColor3fv, PFNGLSECONDARYCOLOR3FVPROC); + USE_GL_FUNC(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC); + USE_GL_FUNC(glGenFramebuffersEXT, PFNGLGENFRAMEBUFFERSEXTPROC); + USE_GL_FUNC(glBindFramebufferEXT, PFNGLBINDFRAMEBUFFEREXTPROC); + USE_GL_FUNC(glDeleteFramebuffersEXT, PFNGLDELETEFRAMEBUFFERSEXTPROC); + USE_GL_FUNC(glCheckFramebufferStatusEXT, PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC); + USE_GL_FUNC(glGetFramebufferAttachmentParameterivEXT, PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC); + USE_GL_FUNC(glFramebufferTexture1DEXT, PFNGLFRAMEBUFFERTEXTURE1DEXTPROC); + USE_GL_FUNC(glFramebufferTexture2DEXT, PFNGLFRAMEBUFFERTEXTURE2DEXTPROC); + USE_GL_FUNC(glFramebufferTexture3DEXT, PFNGLFRAMEBUFFERTEXTURE3DEXTPROC); + USE_GL_FUNC(glGenerateMipmapEXT, PFNGLGENERATEMIPMAPEXTPROC); + USE_GL_FUNC(glGenRenderbuffersEXT, PFNGLGENRENDERBUFFERSEXTPROC); + USE_GL_FUNC(glDeleteRenderbuffersEXT, PFNGLDELETERENDERBUFFERSEXTPROC); + USE_GL_FUNC(glBindRenderbufferEXT, PFNGLBINDRENDERBUFFEREXTPROC); + USE_GL_FUNC(glRenderbufferStorageEXT, PFNGLRENDERBUFFERSTORAGEEXTPROC); + USE_GL_FUNC(glFramebufferRenderbufferEXT, PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC); + USE_GL_FUNC(glClampColorARB, PFNGLCLAMPCOLORARBPROC); + USE_GL_FUNC(glBindFragDataLocationEXT, PFNGLBINDFRAGDATALOCATIONEXTPROC); + +#if !defined(GLX_EXTENSION_NAME) || !defined(GL_VERSION_1_3) + USE_GL_FUNC(glActiveTexture, PFNGLACTIVETEXTUREPROC); + USE_GL_FUNC(glClientActiveTexture, PFNGLACTIVETEXTUREPROC); +#endif + + #undef USE_GL_FUNC +#endif /*__linux__ */ +} + + +namespace __HelperGL { + namespace __Int { + static std::vector split(const std::string &str) + { + std::istringstream ss(str); + std::istream_iterator it(ss); + return std::vector (it, std::istream_iterator()); + } + + /* Sort the vector passed by reference */ + template static inline void sort(std::vector &a) + { + std::sort(a.begin(), a.end()); + } + + /* Compare two vectors */ + template static int equals(std::vector a, std::vector b) + { + if (a.size() != b.size()) return 0; + sort(a); + sort(b); + + return std::equal(a.begin(), a.end(), b.begin()); + } + + template static std::vector getIntersection(std::vector a, std::vector b) + { + sort(a); + sort(b); + + std::vector rc; + std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), + std::back_inserter >(rc)); + return rc; + } + + static std::vector getGLExtensions() + { + std::string extensionsStr( (const char *)glGetString(GL_EXTENSIONS)); + return split (extensionsStr); + } + } + + static int areGLExtensionsSupported(const std::string &extensions) + { + std::vector all = __Int::getGLExtensions(); + + std::vector requested = __Int::split(extensions); + std::vector matched = __Int::getIntersection(all, requested); + + return __Int::equals(matched, requested); + } + + static int isGLVersionSupported(unsigned reqMajor, unsigned reqMinor) + { +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + if (glewInit() != GLEW_OK) + { + std::cerr << "glewInit() failed!" << std::endl; + return 0; + } +#endif + std::string version ((const char *) glGetString (GL_VERSION)); + std::stringstream stream (version); + unsigned major, minor; + char dot; + + stream >> major >> dot >> minor; + + assert (dot == '.'); + return major > reqMajor || (major == reqMajor && minor >= reqMinor); + } + + static inline const char* glErrorToString(GLenum err) + { +#define CASE_RETURN_MACRO(arg) case arg: return #arg + switch(err) + { + CASE_RETURN_MACRO(GL_NO_ERROR); + CASE_RETURN_MACRO(GL_INVALID_ENUM); + CASE_RETURN_MACRO(GL_INVALID_VALUE); + CASE_RETURN_MACRO(GL_INVALID_OPERATION); + CASE_RETURN_MACRO(GL_OUT_OF_MEMORY); + CASE_RETURN_MACRO(GL_STACK_UNDERFLOW); + CASE_RETURN_MACRO(GL_STACK_OVERFLOW); +#ifdef GL_INVALID_FRAMEBUFFER_OPERATION + CASE_RETURN_MACRO(GL_INVALID_FRAMEBUFFER_OPERATION); +#endif + default: break; + } +#undef CASE_RETURN_MACRO + return "*UNKNOWN*"; + } + +//////////////////////////////////////////////////////////////////////////// +//! Check for OpenGL error +//! @return bool if no GL error has been encountered, otherwise 0 +//! @param file __FILE__ macro +//! @param line __LINE__ macro +//! @note The GL error is listed on stderr +//! @note This function should be used via the CHECK_ERROR_GL() macro +//////////////////////////////////////////////////////////////////////////// + inline bool sdkCheckErrorGL(const char *file, const int line) + { + bool ret_val = true; + + // check for error + GLenum gl_error = glGetError(); + + if (gl_error != GL_NO_ERROR) + { +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + char tmpStr[512]; + // NOTE: "%s(%i) : " allows Visual Studio to directly jump to the file at the right line + // when the user double clicks on the error line in the Output pane. Like any compile error. + sprintf_s(tmpStr, 255, "\n%s(%i) : GL Error : %s\n\n", file, line, glErrorToString(gl_error)); + fprintf(stderr, "%s", tmpStr); +#endif + fprintf(stderr, "GL Error in file '%s' in line %d :\n", file, line); + fprintf(stderr, "%s\n", glErrorToString(gl_error)); + ret_val = false; + } + + return ret_val; + } + +#define SDK_CHECK_ERROR_GL() \ + if( false == sdkCheckErrorGL( __FILE__, __LINE__)) { \ + exit(EXIT_FAILURE); \ + } + +} /* of namespace __HelperGL*/ + +using namespace __HelperGL; + +#endif /*HELPER_GL_H*/ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_image.h b/src/cuda/H100/image/recursiveGaussian/helper_image.h new file mode 100644 index 000000000..7e81b0cd8 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_image.h @@ -0,0 +1,1001 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (image,bitmap) +#ifndef COMMON_HELPER_IMAGE_H_ +#define COMMON_HELPER_IMAGE_H_ + +#include +#include "exception.h" +#include +#include + +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a < b) ? a : b) +#endif +#ifndef MAX +#define MAX(a, b) ((a > b) ? a : b) +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#include "helper_string.h" + +// namespace unnamed (internal) +namespace helper_image_internal { +//! size of PGM file header +const unsigned int PGMHeaderSize = 0x40; + +// types + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterFromUByte; + +//! Data converter from unsigned char / unsigned byte +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val); + } +}; + +//! Data converter from unsigned char / unsigned byte to float +template <> +struct ConverterFromUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + float operator()(const unsigned char &val) { + return static_cast(val) / 255.0f; + } +}; + +//! Data converter from unsigned char / unsigned byte to type T +template +struct ConverterToUByte; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator (essentially a passthru + //! @return converted value + //! @param val value to convert + unsigned char operator()(const unsigned char &val) { return val; } +}; + +//! Data converter from unsigned char / unsigned byte to unsigned int +template <> +struct ConverterToUByte { + //! Conversion operator + //! @return converted value + //! @param val value to convert + unsigned char operator()(const float &val) { + return static_cast(val * 255.0f); + } +}; +} // namespace helper_image_internal + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#else +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#endif + +inline bool __loadPPM(const char *file, unsigned char **data, unsigned int *w, + unsigned int *h, unsigned int *channels) { + FILE *fp = NULL; + + if (FOPEN_FAIL(FOPEN(fp, file, "rb"))) { + std::cerr << "__LoadPPM() : Failed to open file: " << file << std::endl; + return false; + } + + // check header + char header[helper_image_internal::PGMHeaderSize]; + + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" << std::endl; + return false; + } + + if (strncmp(header, "P5", 2) == 0) { + *channels = 1; + } else if (strncmp(header, "P6", 2) == 0) { + *channels = 3; + } else { + std::cerr << "__LoadPPM() : File is not a PPM or PGM image" << std::endl; + *channels = 0; + return false; + } + + // parse header, read maxval, width and height + unsigned int width = 0; + unsigned int height = 0; + unsigned int maxval = 0; + unsigned int i = 0; + + while (i < 3) { + if (fgets(header, helper_image_internal::PGMHeaderSize, fp) == NULL) { + std::cerr << "__LoadPPM() : reading PGM header returned NULL" + << std::endl; + return false; + } + + if (header[0] == '#') { + continue; + } + + if (i == 0) { + i += SSCANF(header, "%u %u %u", &width, &height, &maxval); + } else if (i == 1) { + i += SSCANF(header, "%u %u", &height, &maxval); + } else if (i == 2) { + i += SSCANF(header, "%u", &maxval); + } + } + + // check if given handle for the data is initialized + if (NULL != *data) { + if (*w != width || *h != height) { + std::cerr << "__LoadPPM() : Invalid image dimensions." << std::endl; + } + } else { + *data = (unsigned char *)malloc(sizeof(unsigned char) * width * height * + *channels); + *w = width; + *h = height; + } + + // read and close file + if (fread(*data, sizeof(unsigned char), width * height * *channels, fp) == + 0) { + std::cerr << "__LoadPPM() read data returned error." << std::endl; + } + + fclose(fp); + + return true; +} + +template +inline bool sdkLoadPGM(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = NULL; + unsigned int channels; + + if (true != __loadPPM(file, &idata, w, h, &channels)) { + return false; + } + + unsigned int size = *w * *h * channels; + + // initialize mem if necessary + // the correct size is checked / set in loadPGMc() + if (NULL == *data) { + *data = reinterpret_cast(malloc(sizeof(T) * size)); + } + + // copy and cast data + std::transform(idata, idata + size, *data, + helper_image_internal::ConverterFromUByte()); + + free(idata); + + return true; +} + +template +inline bool sdkLoadPPM4(const char *file, T **data, unsigned int *w, + unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = reinterpret_cast(malloc(sizeof(T) * size * 4)); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool __savePPM(const char *file, unsigned char *data, unsigned int w, + unsigned int h, unsigned int channels) { + assert(NULL != data); + assert(w > 0); + assert(h > 0); + + std::fstream fh(file, std::fstream::out | std::fstream::binary); + + if (fh.bad()) { + std::cerr << "__savePPM() : Opening file failed." << std::endl; + return false; + } + + if (channels == 1) { + fh << "P5\n"; + } else if (channels == 3) { + fh << "P6\n"; + } else { + std::cerr << "__savePPM() : Invalid number of channels." << std::endl; + return false; + } + + fh << w << "\n" << h << "\n" << 0xff << std::endl; + + for (unsigned int i = 0; (i < (w * h * channels)) && fh.good(); ++i) { + fh << data[i]; + } + + fh.flush(); + + if (fh.bad()) { + std::cerr << "__savePPM() : Writing data failed." << std::endl; + return false; + } + + fh.close(); + + return true; +} + +template +inline bool sdkSavePGM(const char *file, T *data, unsigned int w, + unsigned int h) { + unsigned int size = w * h; + unsigned char *idata = (unsigned char *)malloc(sizeof(unsigned char) * size); + + std::transform(data, data + size, idata, + helper_image_internal::ConverterToUByte()); + + // write file + bool result = __savePPM(file, idata, w, h, 1); + + // cleanup + free(idata); + + return result; +} + +inline bool sdkSavePPM4ub(const char *file, unsigned char *data, unsigned int w, + unsigned int h) { + // strip 4th component + int size = w * h; + unsigned char *ndata = + (unsigned char *)malloc(sizeof(unsigned char) * size * 3); + unsigned char *ptr = ndata; + + for (int i = 0; i < size; i++) { + *ptr++ = *data++; + *ptr++ = *data++; + *ptr++ = *data++; + data++; + } + + bool result = __savePPM(file, ndata, w, h, 3); + free(ndata); + return result; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFile(const char *filename, T **data, unsigned int *len, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // intermediate storage for the data read + std::vector data_read; + + // open file for reading + FILE *fh = NULL; + + // check if filestream is valid + if (FOPEN_FAIL(FOPEN(fh, filename, "r"))) { + printf("Unable to open input file: %s\n", filename); + return false; + } + + // read all data elements + T token; + + while (!feof(fh)) { + fscanf(fh, "%f", &token); + data_read.push_back(token); + } + + // the last element is read twice + data_read.pop_back(); + fclose(fh); + + // check if the given handle is already initialized + if (NULL != *data) { + if (*len != data_read.size()) { + std::cerr << "sdkReadFile() : Initialized memory given but " + << "size mismatch with signal read " + << "(data read / data init = " << (unsigned int)data_read.size() + << " / " << *len << ")" << std::endl; + + return false; + } + } else { + // allocate storage for the data read + *data = reinterpret_cast(malloc(sizeof(T) * data_read.size())); + // store signal size + *len = static_cast(data_read.size()); + } + + // copy data + memcpy(*data, &data_read.front(), sizeof(T) * data_read.size()); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Read file \filename and return the data +//! @return bool if reading the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data uninitialized pointer, returned initialized and pointing to +//! the data read +//! @param len number of data elements in data, -1 on error +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkReadFileBlocks(const char *filename, T **data, unsigned int *len, + unsigned int block_num, unsigned int block_size, + bool verbose) { + // check input arguments + assert(NULL != filename); + assert(NULL != len); + + // open file for reading + FILE *fh = fopen(filename, "rb"); + + if (fh == NULL && verbose) { + std::cerr << "sdkReadFile() : Opening file failed." << std::endl; + return false; + } + + // check if the given handle is already initialized + // allocate storage for the data read + data[block_num] = reinterpret_cast(malloc(block_size)); + + // read all data elements + fseek(fh, block_num * block_size, SEEK_SET); + *len = fread(data[block_num], sizeof(T), block_size / sizeof(T), fh); + + fclose(fh); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Write a data file \filename +//! @return true if writing the file succeeded, otherwise false +//! @param filename name of the source file +//! @param data data to write +//! @param len number of data elements in data, -1 on error +//! @param epsilon epsilon for comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool sdkWriteFile(const char *filename, const T *data, unsigned int len, + const S epsilon, bool verbose, bool append = false) { + assert(NULL != filename); + assert(NULL != data); + + // open file for writing + // if (append) { + std::fstream fh(filename, std::fstream::out | std::fstream::ate); + + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename + << " for write/append." << std::endl; + } + + /* } else { + std::fstream fh(filename, std::fstream::out); + if (verbose) { + std::cerr << "sdkWriteFile() : Open file " << filename << " for + write." << std::endl; + } + } + */ + + // check if filestream is valid + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Opening file failed." << std::endl; + } + + return false; + } + + // first write epsilon + fh << "# " << epsilon << "\n"; + + // write data + for (unsigned int i = 0; (i < len) && (fh.good()); ++i) { + fh << data[i] << ' '; + } + + // Check if writing succeeded + if (!fh.good()) { + if (verbose) { + std::cerr << "sdkWriteFile() : Writing file failed." << std::endl; + } + + return false; + } + + // file ends with nl + fh << std::endl; + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference timer_interface to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareData(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + bool result = true; + unsigned int error_count = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = static_cast(reference[i]) - static_cast(data[i]); + bool comp = (diff <= epsilon) && (diff >= -epsilon); + result &= comp; + + error_count += !comp; + +#if 0 + + if (!comp) { + std::cerr << "ERROR, i = " << i << ",\t " + << reference[i] << " / " + << data[i] + << " (reference / data)\n"; + } + +#endif + } + + if (threshold == 0.0f) { + return (result) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return (len * threshold > error_count) ? true : false; + } +} + +#ifndef __MIN_EPSILON_ERROR +#define __MIN_EPSILON_ERROR 1e-3f +#endif + +////////////////////////////////////////////////////////////////////////////// +//! Compare two arrays of arbitrary type +//! @return true if \a reference and \a data are identical, otherwise false +//! @param reference handle to the reference data / gold image +//! @param data handle to the computed data +//! @param len number of elements in reference and data +//! @param epsilon epsilon to use for the comparison +//! @param epsilon threshold % of (# of bytes) for pass/fail +////////////////////////////////////////////////////////////////////////////// +template +inline bool compareDataAsFloatThreshold(const T *reference, const T *data, + const unsigned int len, const S epsilon, + const float threshold) { + assert(epsilon >= 0); + + // If we set epsilon to be 0, let's set a minimum threshold + float max_error = MAX((float)epsilon, __MIN_EPSILON_ERROR); + int error_count = 0; + bool result = true; + + for (unsigned int i = 0; i < len; ++i) { + float diff = + fabs(static_cast(reference[i]) - static_cast(data[i])); + bool comp = (diff < max_error); + result &= comp; + + if (!comp) { + error_count++; + } + } + + if (threshold == 0.0f) { + if (error_count) { + printf("total # of errors = %d\n", error_count); + } + + return (error_count == 0) ? true : false; + } else { + if (error_count) { + printf("%4.2f(%%) of bytes mismatched (count=%d)\n", + static_cast(error_count) * 100 / static_cast(len), + error_count); + } + + return ((len * threshold > error_count) ? true : false); + } +} + +inline void sdkDumpBin(void *data, unsigned int bytes, const char *filename) { + printf("sdkDumpBin: <%s>\n", filename); + FILE *fp; + FOPEN(fp, filename, "wb"); + fwrite(data, bytes, 1, fp); + fflush(fp); + fclose(fp); +} + +inline bool sdkCompareBin2BinUint(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + unsigned int *src_buffer, *ref_buffer; + FILE *src_fp = NULL, *ref_fp = NULL; + + uint64_t error_count = 0; + size_t fsize = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", + src_file); + error_count++; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", + ref_file, exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + ref_file); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf( + "compareBin2Bin " + " unable to open ref_file: %s\n", + ref_file_path); + error_count++; + } + + if (src_fp && ref_fp) { + src_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + ref_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); + + fsize = fread(src_buffer, nelements, sizeof(unsigned int), src_fp); + fsize = fread(ref_buffer, nelements, sizeof(unsigned int), ref_fp); + + printf( + "> compareBin2Bin nelements=%d," + " epsilon=%4.2f, threshold=%4.2f\n", + nelements, epsilon, threshold); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize)); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize)); + + if (!compareData(ref_buffer, src_buffer, nelements, + epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareBin2BinFloat(const char *src_file, const char *ref_file, + unsigned int nelements, const float epsilon, + const float threshold, char *exec_path) { + float *src_buffer = NULL, *ref_buffer = NULL; + FILE *src_fp = NULL, *ref_fp = NULL; + size_t fsize = 0; + + uint64_t error_count = 0; + + if (FOPEN_FAIL(FOPEN(src_fp, src_file, "rb"))) { + printf("compareBin2Bin unable to open src_file: %s\n", src_file); + error_count = 1; + } + + char *ref_file_path = sdkFindFilePath(ref_file, exec_path); + + if (ref_file_path == NULL) { + printf("compareBin2Bin unable to find <%s> in <%s>\n", ref_file, + exec_path); + printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", + exec_path); + printf("Aborting comparison!\n"); + printf(" FAILED\n"); + error_count++; + + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } else { + if (FOPEN_FAIL(FOPEN(ref_fp, ref_file_path, "rb"))) { + printf("compareBin2Bin unable to open ref_file: %s\n", + ref_file_path); + error_count = 1; + } + + if (src_fp && ref_fp) { + src_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + ref_buffer = reinterpret_cast(malloc(nelements * sizeof(float))); + + printf( + "> compareBin2Bin nelements=%d, epsilon=%4.2f," + " threshold=%4.2f\n", + nelements, epsilon, threshold); + fsize = fread(src_buffer, sizeof(float), nelements, src_fp); + printf(" src_file <%s>, size=%d bytes\n", src_file, + static_cast(fsize * sizeof(float))); + fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp); + printf(" ref_file <%s>, size=%d bytes\n", ref_file_path, + static_cast(fsize * sizeof(float))); + + if (!compareDataAsFloatThreshold( + ref_buffer, src_buffer, nelements, epsilon, threshold)) { + error_count++; + } + + fclose(src_fp); + fclose(ref_fp); + + free(src_buffer); + free(ref_buffer); + } else { + if (src_fp) { + fclose(src_fp); + } + + if (ref_fp) { + fclose(ref_fp); + } + } + } + + if (error_count == 0) { + printf(" OK\n"); + } else { + printf(" FAILURE: %d errors...\n", (unsigned int)error_count); + } + + return (error_count == 0); // returns true if all pixels pass +} + +inline bool sdkCompareL2fe(const float *reference, const float *data, + const unsigned int len, const float epsilon) { + assert(epsilon >= 0); + + float error = 0; + float ref = 0; + + for (unsigned int i = 0; i < len; ++i) { + float diff = reference[i] - data[i]; + error += diff * diff; + ref += reference[i] * reference[i]; + } + + float normRef = sqrtf(ref); + + if (fabs(ref) < 1e-7) { +#ifdef _DEBUG + std::cerr << "ERROR, reference l2-norm is 0\n"; +#endif + return false; + } + + float normError = sqrtf(error); + error = normError / normRef; + bool result = error < epsilon; +#ifdef _DEBUG + + if (!result) { + std::cerr << "ERROR, l2-norm error " << error << " is greater than epsilon " + << epsilon << "\n"; + } + +#endif + + return result; +} + +inline bool sdkLoadPPMub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned int channels; + return __loadPPM(file, data, w, h, &channels); +} + +inline bool sdkLoadPPM4ub(const char *file, unsigned char **data, + unsigned int *w, unsigned int *h) { + unsigned char *idata = 0; + unsigned int channels; + + if (__loadPPM(file, &idata, w, h, &channels)) { + // pad 4th component + int size = *w * *h; + // keep the original pointer + unsigned char *idata_orig = idata; + *data = (unsigned char *)malloc(sizeof(unsigned char) * size * 4); + unsigned char *ptr = *data; + + for (int i = 0; i < size; i++) { + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = *idata++; + *ptr++ = 0; + } + + free(idata_orig); + return true; + } else { + free(idata); + return false; + } +} + +inline bool sdkComparePPM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data, *ref_data; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPM4ub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPM4ub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PPMvsPPM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PPMvsPPM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) { + std::cerr << "PPMvsPPM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + } + + if (compareData(ref_data, src_data, src_width * src_height * 4, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +inline bool sdkComparePGM(const char *src_file, const char *ref_file, + const float epsilon, const float threshold, + bool verboseErrors) { + unsigned char *src_data = 0, *ref_data = 0; + uint64_t error_count = 0; + unsigned int ref_width, ref_height; + unsigned int src_width, src_height; + + if (src_file == NULL || ref_file == NULL) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: src_file or ref_file is NULL." + " Aborting comparison\n"; + } + + return false; + } + + if (verboseErrors) { + std::cerr << "> Compare (a)rendered: <" << src_file << ">\n"; + std::cerr << "> (b)reference: <" << ref_file << ">\n"; + } + + if (sdkLoadPPMub(ref_file, &ref_data, &ref_width, &ref_height) != true) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: unable to load ref image file: " << ref_file + << "\n"; + } + + return false; + } + + if (sdkLoadPPMub(src_file, &src_data, &src_width, &src_height) != true) { + std::cerr << "PGMvsPGM: unable to load src image file: " << src_file + << "\n"; + return false; + } + + if (src_height != ref_height || src_width != ref_width) { + if (verboseErrors) { + std::cerr << "PGMvsPGM: source and ref size mismatch (" << src_width + << "," << src_height << ")vs(" << ref_width << "," << ref_height + << ")\n"; + } + } + + if (verboseErrors) + std::cerr << "PGMvsPGM: comparing images size (" << src_width << "," + << src_height << ") epsilon(" << epsilon << "), threshold(" + << threshold * 100 << "%)\n"; + + if (compareData(ref_data, src_data, src_width * src_height, epsilon, + threshold) == false) { + error_count = 1; + } + + if (error_count == 0) { + if (verboseErrors) { + std::cerr << " OK\n\n"; + } + } else { + if (verboseErrors) { + std::cerr << " FAILURE! " << error_count << " errors...\n\n"; + } + } + + // returns true if all pixels pass + return (error_count == 0) ? true : false; +} + +#endif // COMMON_HELPER_IMAGE_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_math.h b/src/cuda/H100/image/recursiveGaussian/helper_math.h new file mode 100644 index 000000000..6d2f63aee --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_math.h @@ -0,0 +1,1469 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file implements common mathematical operations on vector types + * (float3, float4 etc.) since these are not provided as standard by CUDA. + * + * The syntax is modeled on the Cg standard library. + * + * This is part of the Helper library includes + * + * Thanks to Linh Hah for additions and fixes. + */ + +#ifndef HELPER_MATH_H +#define HELPER_MATH_H + +#include "cuda_runtime.h" + +typedef unsigned int uint; +typedef unsigned short ushort; + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +#ifndef __CUDACC__ +#include + +//////////////////////////////////////////////////////////////////////////////// +// host implementations of CUDA functions +//////////////////////////////////////////////////////////////////////////////// + +inline float fminf(float a, float b) +{ + return a < b ? a : b; +} + +inline float fmaxf(float a, float b) +{ + return a > b ? a : b; +} + +inline int max(int a, int b) +{ + return a > b ? a : b; +} + +inline int min(int a, int b) +{ + return a < b ? a : b; +} + +inline float rsqrtf(float x) +{ + return 1.0f / sqrtf(x); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// constructors +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 make_float2(float s) +{ + return make_float2(s, s); +} +inline __host__ __device__ float2 make_float2(float3 a) +{ + return make_float2(a.x, a.y); +} +inline __host__ __device__ float2 make_float2(int2 a) +{ + return make_float2(float(a.x), float(a.y)); +} +inline __host__ __device__ float2 make_float2(uint2 a) +{ + return make_float2(float(a.x), float(a.y)); +} + +inline __host__ __device__ int2 make_int2(int s) +{ + return make_int2(s, s); +} +inline __host__ __device__ int2 make_int2(int3 a) +{ + return make_int2(a.x, a.y); +} +inline __host__ __device__ int2 make_int2(uint2 a) +{ + return make_int2(int(a.x), int(a.y)); +} +inline __host__ __device__ int2 make_int2(float2 a) +{ + return make_int2(int(a.x), int(a.y)); +} + +inline __host__ __device__ uint2 make_uint2(uint s) +{ + return make_uint2(s, s); +} +inline __host__ __device__ uint2 make_uint2(uint3 a) +{ + return make_uint2(a.x, a.y); +} +inline __host__ __device__ uint2 make_uint2(int2 a) +{ + return make_uint2(uint(a.x), uint(a.y)); +} + +inline __host__ __device__ float3 make_float3(float s) +{ + return make_float3(s, s, s); +} +inline __host__ __device__ float3 make_float3(float2 a) +{ + return make_float3(a.x, a.y, 0.0f); +} +inline __host__ __device__ float3 make_float3(float2 a, float s) +{ + return make_float3(a.x, a.y, s); +} +inline __host__ __device__ float3 make_float3(float4 a) +{ + return make_float3(a.x, a.y, a.z); +} +inline __host__ __device__ float3 make_float3(int3 a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} +inline __host__ __device__ float3 make_float3(uint3 a) +{ + return make_float3(float(a.x), float(a.y), float(a.z)); +} + +inline __host__ __device__ int3 make_int3(int s) +{ + return make_int3(s, s, s); +} +inline __host__ __device__ int3 make_int3(int2 a) +{ + return make_int3(a.x, a.y, 0); +} +inline __host__ __device__ int3 make_int3(int2 a, int s) +{ + return make_int3(a.x, a.y, s); +} +inline __host__ __device__ int3 make_int3(uint3 a) +{ + return make_int3(int(a.x), int(a.y), int(a.z)); +} +inline __host__ __device__ int3 make_int3(float3 a) +{ + return make_int3(int(a.x), int(a.y), int(a.z)); +} + +inline __host__ __device__ uint3 make_uint3(uint s) +{ + return make_uint3(s, s, s); +} +inline __host__ __device__ uint3 make_uint3(uint2 a) +{ + return make_uint3(a.x, a.y, 0); +} +inline __host__ __device__ uint3 make_uint3(uint2 a, uint s) +{ + return make_uint3(a.x, a.y, s); +} +inline __host__ __device__ uint3 make_uint3(uint4 a) +{ + return make_uint3(a.x, a.y, a.z); +} +inline __host__ __device__ uint3 make_uint3(int3 a) +{ + return make_uint3(uint(a.x), uint(a.y), uint(a.z)); +} + +inline __host__ __device__ float4 make_float4(float s) +{ + return make_float4(s, s, s, s); +} +inline __host__ __device__ float4 make_float4(float3 a) +{ + return make_float4(a.x, a.y, a.z, 0.0f); +} +inline __host__ __device__ float4 make_float4(float3 a, float w) +{ + return make_float4(a.x, a.y, a.z, w); +} +inline __host__ __device__ float4 make_float4(int4 a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} +inline __host__ __device__ float4 make_float4(uint4 a) +{ + return make_float4(float(a.x), float(a.y), float(a.z), float(a.w)); +} + +inline __host__ __device__ int4 make_int4(int s) +{ + return make_int4(s, s, s, s); +} +inline __host__ __device__ int4 make_int4(int3 a) +{ + return make_int4(a.x, a.y, a.z, 0); +} +inline __host__ __device__ int4 make_int4(int3 a, int w) +{ + return make_int4(a.x, a.y, a.z, w); +} +inline __host__ __device__ int4 make_int4(uint4 a) +{ + return make_int4(int(a.x), int(a.y), int(a.z), int(a.w)); +} +inline __host__ __device__ int4 make_int4(float4 a) +{ + return make_int4(int(a.x), int(a.y), int(a.z), int(a.w)); +} + + +inline __host__ __device__ uint4 make_uint4(uint s) +{ + return make_uint4(s, s, s, s); +} +inline __host__ __device__ uint4 make_uint4(uint3 a) +{ + return make_uint4(a.x, a.y, a.z, 0); +} +inline __host__ __device__ uint4 make_uint4(uint3 a, uint w) +{ + return make_uint4(a.x, a.y, a.z, w); +} +inline __host__ __device__ uint4 make_uint4(int4 a) +{ + return make_uint4(uint(a.x), uint(a.y), uint(a.z), uint(a.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// negate +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 operator-(float2 &a) +{ + return make_float2(-a.x, -a.y); +} +inline __host__ __device__ int2 operator-(int2 &a) +{ + return make_int2(-a.x, -a.y); +} +inline __host__ __device__ float3 operator-(float3 &a) +{ + return make_float3(-a.x, -a.y, -a.z); +} +inline __host__ __device__ int3 operator-(int3 &a) +{ + return make_int3(-a.x, -a.y, -a.z); +} +inline __host__ __device__ float4 operator-(float4 &a) +{ + return make_float4(-a.x, -a.y, -a.z, -a.w); +} +inline __host__ __device__ int4 operator-(int4 &a) +{ + return make_int4(-a.x, -a.y, -a.z, -a.w); +} + +//////////////////////////////////////////////////////////////////////////////// +// addition +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 operator+(float2 a, float2 b) +{ + return make_float2(a.x + b.x, a.y + b.y); +} +inline __host__ __device__ void operator+=(float2 &a, float2 b) +{ + a.x += b.x; + a.y += b.y; +} +inline __host__ __device__ float2 operator+(float2 a, float b) +{ + return make_float2(a.x + b, a.y + b); +} +inline __host__ __device__ float2 operator+(float b, float2 a) +{ + return make_float2(a.x + b, a.y + b); +} +inline __host__ __device__ void operator+=(float2 &a, float b) +{ + a.x += b; + a.y += b; +} + +inline __host__ __device__ int2 operator+(int2 a, int2 b) +{ + return make_int2(a.x + b.x, a.y + b.y); +} +inline __host__ __device__ void operator+=(int2 &a, int2 b) +{ + a.x += b.x; + a.y += b.y; +} +inline __host__ __device__ int2 operator+(int2 a, int b) +{ + return make_int2(a.x + b, a.y + b); +} +inline __host__ __device__ int2 operator+(int b, int2 a) +{ + return make_int2(a.x + b, a.y + b); +} +inline __host__ __device__ void operator+=(int2 &a, int b) +{ + a.x += b; + a.y += b; +} + +inline __host__ __device__ uint2 operator+(uint2 a, uint2 b) +{ + return make_uint2(a.x + b.x, a.y + b.y); +} +inline __host__ __device__ void operator+=(uint2 &a, uint2 b) +{ + a.x += b.x; + a.y += b.y; +} +inline __host__ __device__ uint2 operator+(uint2 a, uint b) +{ + return make_uint2(a.x + b, a.y + b); +} +inline __host__ __device__ uint2 operator+(uint b, uint2 a) +{ + return make_uint2(a.x + b, a.y + b); +} +inline __host__ __device__ void operator+=(uint2 &a, uint b) +{ + a.x += b; + a.y += b; +} + + +inline __host__ __device__ float3 operator+(float3 a, float3 b) +{ + return make_float3(a.x + b.x, a.y + b.y, a.z + b.z); +} +inline __host__ __device__ void operator+=(float3 &a, float3 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +inline __host__ __device__ float3 operator+(float3 a, float b) +{ + return make_float3(a.x + b, a.y + b, a.z + b); +} +inline __host__ __device__ void operator+=(float3 &a, float b) +{ + a.x += b; + a.y += b; + a.z += b; +} + +inline __host__ __device__ int3 operator+(int3 a, int3 b) +{ + return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} +inline __host__ __device__ void operator+=(int3 &a, int3 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +inline __host__ __device__ int3 operator+(int3 a, int b) +{ + return make_int3(a.x + b, a.y + b, a.z + b); +} +inline __host__ __device__ void operator+=(int3 &a, int b) +{ + a.x += b; + a.y += b; + a.z += b; +} + +inline __host__ __device__ uint3 operator+(uint3 a, uint3 b) +{ + return make_uint3(a.x + b.x, a.y + b.y, a.z + b.z); +} +inline __host__ __device__ void operator+=(uint3 &a, uint3 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +inline __host__ __device__ uint3 operator+(uint3 a, uint b) +{ + return make_uint3(a.x + b, a.y + b, a.z + b); +} +inline __host__ __device__ void operator+=(uint3 &a, uint b) +{ + a.x += b; + a.y += b; + a.z += b; +} + +inline __host__ __device__ int3 operator+(int b, int3 a) +{ + return make_int3(a.x + b, a.y + b, a.z + b); +} +inline __host__ __device__ uint3 operator+(uint b, uint3 a) +{ + return make_uint3(a.x + b, a.y + b, a.z + b); +} +inline __host__ __device__ float3 operator+(float b, float3 a) +{ + return make_float3(a.x + b, a.y + b, a.z + b); +} + +inline __host__ __device__ float4 operator+(float4 a, float4 b) +{ + return make_float4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +inline __host__ __device__ void operator+=(float4 &a, float4 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +inline __host__ __device__ float4 operator+(float4 a, float b) +{ + return make_float4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ float4 operator+(float b, float4 a) +{ + return make_float4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ void operator+=(float4 &a, float b) +{ + a.x += b; + a.y += b; + a.z += b; + a.w += b; +} + +inline __host__ __device__ int4 operator+(int4 a, int4 b) +{ + return make_int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +inline __host__ __device__ void operator+=(int4 &a, int4 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +inline __host__ __device__ int4 operator+(int4 a, int b) +{ + return make_int4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ int4 operator+(int b, int4 a) +{ + return make_int4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ void operator+=(int4 &a, int b) +{ + a.x += b; + a.y += b; + a.z += b; + a.w += b; +} + +inline __host__ __device__ uint4 operator+(uint4 a, uint4 b) +{ + return make_uint4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} +inline __host__ __device__ void operator+=(uint4 &a, uint4 b) +{ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +inline __host__ __device__ uint4 operator+(uint4 a, uint b) +{ + return make_uint4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ uint4 operator+(uint b, uint4 a) +{ + return make_uint4(a.x + b, a.y + b, a.z + b, a.w + b); +} +inline __host__ __device__ void operator+=(uint4 &a, uint b) +{ + a.x += b; + a.y += b; + a.z += b; + a.w += b; +} + +//////////////////////////////////////////////////////////////////////////////// +// subtract +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 operator-(float2 a, float2 b) +{ + return make_float2(a.x - b.x, a.y - b.y); +} +inline __host__ __device__ void operator-=(float2 &a, float2 b) +{ + a.x -= b.x; + a.y -= b.y; +} +inline __host__ __device__ float2 operator-(float2 a, float b) +{ + return make_float2(a.x - b, a.y - b); +} +inline __host__ __device__ float2 operator-(float b, float2 a) +{ + return make_float2(b - a.x, b - a.y); +} +inline __host__ __device__ void operator-=(float2 &a, float b) +{ + a.x -= b; + a.y -= b; +} + +inline __host__ __device__ int2 operator-(int2 a, int2 b) +{ + return make_int2(a.x - b.x, a.y - b.y); +} +inline __host__ __device__ void operator-=(int2 &a, int2 b) +{ + a.x -= b.x; + a.y -= b.y; +} +inline __host__ __device__ int2 operator-(int2 a, int b) +{ + return make_int2(a.x - b, a.y - b); +} +inline __host__ __device__ int2 operator-(int b, int2 a) +{ + return make_int2(b - a.x, b - a.y); +} +inline __host__ __device__ void operator-=(int2 &a, int b) +{ + a.x -= b; + a.y -= b; +} + +inline __host__ __device__ uint2 operator-(uint2 a, uint2 b) +{ + return make_uint2(a.x - b.x, a.y - b.y); +} +inline __host__ __device__ void operator-=(uint2 &a, uint2 b) +{ + a.x -= b.x; + a.y -= b.y; +} +inline __host__ __device__ uint2 operator-(uint2 a, uint b) +{ + return make_uint2(a.x - b, a.y - b); +} +inline __host__ __device__ uint2 operator-(uint b, uint2 a) +{ + return make_uint2(b - a.x, b - a.y); +} +inline __host__ __device__ void operator-=(uint2 &a, uint b) +{ + a.x -= b; + a.y -= b; +} + +inline __host__ __device__ float3 operator-(float3 a, float3 b) +{ + return make_float3(a.x - b.x, a.y - b.y, a.z - b.z); +} +inline __host__ __device__ void operator-=(float3 &a, float3 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +inline __host__ __device__ float3 operator-(float3 a, float b) +{ + return make_float3(a.x - b, a.y - b, a.z - b); +} +inline __host__ __device__ float3 operator-(float b, float3 a) +{ + return make_float3(b - a.x, b - a.y, b - a.z); +} +inline __host__ __device__ void operator-=(float3 &a, float b) +{ + a.x -= b; + a.y -= b; + a.z -= b; +} + +inline __host__ __device__ int3 operator-(int3 a, int3 b) +{ + return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); +} +inline __host__ __device__ void operator-=(int3 &a, int3 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +inline __host__ __device__ int3 operator-(int3 a, int b) +{ + return make_int3(a.x - b, a.y - b, a.z - b); +} +inline __host__ __device__ int3 operator-(int b, int3 a) +{ + return make_int3(b - a.x, b - a.y, b - a.z); +} +inline __host__ __device__ void operator-=(int3 &a, int b) +{ + a.x -= b; + a.y -= b; + a.z -= b; +} + +inline __host__ __device__ uint3 operator-(uint3 a, uint3 b) +{ + return make_uint3(a.x - b.x, a.y - b.y, a.z - b.z); +} +inline __host__ __device__ void operator-=(uint3 &a, uint3 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +inline __host__ __device__ uint3 operator-(uint3 a, uint b) +{ + return make_uint3(a.x - b, a.y - b, a.z - b); +} +inline __host__ __device__ uint3 operator-(uint b, uint3 a) +{ + return make_uint3(b - a.x, b - a.y, b - a.z); +} +inline __host__ __device__ void operator-=(uint3 &a, uint b) +{ + a.x -= b; + a.y -= b; + a.z -= b; +} + +inline __host__ __device__ float4 operator-(float4 a, float4 b) +{ + return make_float4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +inline __host__ __device__ void operator-=(float4 &a, float4 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +inline __host__ __device__ float4 operator-(float4 a, float b) +{ + return make_float4(a.x - b, a.y - b, a.z - b, a.w - b); +} +inline __host__ __device__ void operator-=(float4 &a, float b) +{ + a.x -= b; + a.y -= b; + a.z -= b; + a.w -= b; +} + +inline __host__ __device__ int4 operator-(int4 a, int4 b) +{ + return make_int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +inline __host__ __device__ void operator-=(int4 &a, int4 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +inline __host__ __device__ int4 operator-(int4 a, int b) +{ + return make_int4(a.x - b, a.y - b, a.z - b, a.w - b); +} +inline __host__ __device__ int4 operator-(int b, int4 a) +{ + return make_int4(b - a.x, b - a.y, b - a.z, b - a.w); +} +inline __host__ __device__ void operator-=(int4 &a, int b) +{ + a.x -= b; + a.y -= b; + a.z -= b; + a.w -= b; +} + +inline __host__ __device__ uint4 operator-(uint4 a, uint4 b) +{ + return make_uint4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} +inline __host__ __device__ void operator-=(uint4 &a, uint4 b) +{ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +inline __host__ __device__ uint4 operator-(uint4 a, uint b) +{ + return make_uint4(a.x - b, a.y - b, a.z - b, a.w - b); +} +inline __host__ __device__ uint4 operator-(uint b, uint4 a) +{ + return make_uint4(b - a.x, b - a.y, b - a.z, b - a.w); +} +inline __host__ __device__ void operator-=(uint4 &a, uint b) +{ + a.x -= b; + a.y -= b; + a.z -= b; + a.w -= b; +} + +//////////////////////////////////////////////////////////////////////////////// +// multiply +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 operator*(float2 a, float2 b) +{ + return make_float2(a.x * b.x, a.y * b.y); +} +inline __host__ __device__ void operator*=(float2 &a, float2 b) +{ + a.x *= b.x; + a.y *= b.y; +} +inline __host__ __device__ float2 operator*(float2 a, float b) +{ + return make_float2(a.x * b, a.y * b); +} +inline __host__ __device__ float2 operator*(float b, float2 a) +{ + return make_float2(b * a.x, b * a.y); +} +inline __host__ __device__ void operator*=(float2 &a, float b) +{ + a.x *= b; + a.y *= b; +} + +inline __host__ __device__ int2 operator*(int2 a, int2 b) +{ + return make_int2(a.x * b.x, a.y * b.y); +} +inline __host__ __device__ void operator*=(int2 &a, int2 b) +{ + a.x *= b.x; + a.y *= b.y; +} +inline __host__ __device__ int2 operator*(int2 a, int b) +{ + return make_int2(a.x * b, a.y * b); +} +inline __host__ __device__ int2 operator*(int b, int2 a) +{ + return make_int2(b * a.x, b * a.y); +} +inline __host__ __device__ void operator*=(int2 &a, int b) +{ + a.x *= b; + a.y *= b; +} + +inline __host__ __device__ uint2 operator*(uint2 a, uint2 b) +{ + return make_uint2(a.x * b.x, a.y * b.y); +} +inline __host__ __device__ void operator*=(uint2 &a, uint2 b) +{ + a.x *= b.x; + a.y *= b.y; +} +inline __host__ __device__ uint2 operator*(uint2 a, uint b) +{ + return make_uint2(a.x * b, a.y * b); +} +inline __host__ __device__ uint2 operator*(uint b, uint2 a) +{ + return make_uint2(b * a.x, b * a.y); +} +inline __host__ __device__ void operator*=(uint2 &a, uint b) +{ + a.x *= b; + a.y *= b; +} + +inline __host__ __device__ float3 operator*(float3 a, float3 b) +{ + return make_float3(a.x * b.x, a.y * b.y, a.z * b.z); +} +inline __host__ __device__ void operator*=(float3 &a, float3 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; +} +inline __host__ __device__ float3 operator*(float3 a, float b) +{ + return make_float3(a.x * b, a.y * b, a.z * b); +} +inline __host__ __device__ float3 operator*(float b, float3 a) +{ + return make_float3(b * a.x, b * a.y, b * a.z); +} +inline __host__ __device__ void operator*=(float3 &a, float b) +{ + a.x *= b; + a.y *= b; + a.z *= b; +} + +inline __host__ __device__ int3 operator*(int3 a, int3 b) +{ + return make_int3(a.x * b.x, a.y * b.y, a.z * b.z); +} +inline __host__ __device__ void operator*=(int3 &a, int3 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; +} +inline __host__ __device__ int3 operator*(int3 a, int b) +{ + return make_int3(a.x * b, a.y * b, a.z * b); +} +inline __host__ __device__ int3 operator*(int b, int3 a) +{ + return make_int3(b * a.x, b * a.y, b * a.z); +} +inline __host__ __device__ void operator*=(int3 &a, int b) +{ + a.x *= b; + a.y *= b; + a.z *= b; +} + +inline __host__ __device__ uint3 operator*(uint3 a, uint3 b) +{ + return make_uint3(a.x * b.x, a.y * b.y, a.z * b.z); +} +inline __host__ __device__ void operator*=(uint3 &a, uint3 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; +} +inline __host__ __device__ uint3 operator*(uint3 a, uint b) +{ + return make_uint3(a.x * b, a.y * b, a.z * b); +} +inline __host__ __device__ uint3 operator*(uint b, uint3 a) +{ + return make_uint3(b * a.x, b * a.y, b * a.z); +} +inline __host__ __device__ void operator*=(uint3 &a, uint b) +{ + a.x *= b; + a.y *= b; + a.z *= b; +} + +inline __host__ __device__ float4 operator*(float4 a, float4 b) +{ + return make_float4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +inline __host__ __device__ void operator*=(float4 &a, float4 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + a.w *= b.w; +} +inline __host__ __device__ float4 operator*(float4 a, float b) +{ + return make_float4(a.x * b, a.y * b, a.z * b, a.w * b); +} +inline __host__ __device__ float4 operator*(float b, float4 a) +{ + return make_float4(b * a.x, b * a.y, b * a.z, b * a.w); +} +inline __host__ __device__ void operator*=(float4 &a, float b) +{ + a.x *= b; + a.y *= b; + a.z *= b; + a.w *= b; +} + +inline __host__ __device__ int4 operator*(int4 a, int4 b) +{ + return make_int4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +inline __host__ __device__ void operator*=(int4 &a, int4 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + a.w *= b.w; +} +inline __host__ __device__ int4 operator*(int4 a, int b) +{ + return make_int4(a.x * b, a.y * b, a.z * b, a.w * b); +} +inline __host__ __device__ int4 operator*(int b, int4 a) +{ + return make_int4(b * a.x, b * a.y, b * a.z, b * a.w); +} +inline __host__ __device__ void operator*=(int4 &a, int b) +{ + a.x *= b; + a.y *= b; + a.z *= b; + a.w *= b; +} + +inline __host__ __device__ uint4 operator*(uint4 a, uint4 b) +{ + return make_uint4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +inline __host__ __device__ void operator*=(uint4 &a, uint4 b) +{ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + a.w *= b.w; +} +inline __host__ __device__ uint4 operator*(uint4 a, uint b) +{ + return make_uint4(a.x * b, a.y * b, a.z * b, a.w * b); +} +inline __host__ __device__ uint4 operator*(uint b, uint4 a) +{ + return make_uint4(b * a.x, b * a.y, b * a.z, b * a.w); +} +inline __host__ __device__ void operator*=(uint4 &a, uint b) +{ + a.x *= b; + a.y *= b; + a.z *= b; + a.w *= b; +} + +//////////////////////////////////////////////////////////////////////////////// +// divide +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 operator/(float2 a, float2 b) +{ + return make_float2(a.x / b.x, a.y / b.y); +} +inline __host__ __device__ void operator/=(float2 &a, float2 b) +{ + a.x /= b.x; + a.y /= b.y; +} +inline __host__ __device__ float2 operator/(float2 a, float b) +{ + return make_float2(a.x / b, a.y / b); +} +inline __host__ __device__ void operator/=(float2 &a, float b) +{ + a.x /= b; + a.y /= b; +} +inline __host__ __device__ float2 operator/(float b, float2 a) +{ + return make_float2(b / a.x, b / a.y); +} + +inline __host__ __device__ float3 operator/(float3 a, float3 b) +{ + return make_float3(a.x / b.x, a.y / b.y, a.z / b.z); +} +inline __host__ __device__ void operator/=(float3 &a, float3 b) +{ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; +} +inline __host__ __device__ float3 operator/(float3 a, float b) +{ + return make_float3(a.x / b, a.y / b, a.z / b); +} +inline __host__ __device__ void operator/=(float3 &a, float b) +{ + a.x /= b; + a.y /= b; + a.z /= b; +} +inline __host__ __device__ float3 operator/(float b, float3 a) +{ + return make_float3(b / a.x, b / a.y, b / a.z); +} + +inline __host__ __device__ float4 operator/(float4 a, float4 b) +{ + return make_float4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} +inline __host__ __device__ void operator/=(float4 &a, float4 b) +{ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; + a.w /= b.w; +} +inline __host__ __device__ float4 operator/(float4 a, float b) +{ + return make_float4(a.x / b, a.y / b, a.z / b, a.w / b); +} +inline __host__ __device__ void operator/=(float4 &a, float b) +{ + a.x /= b; + a.y /= b; + a.z /= b; + a.w /= b; +} +inline __host__ __device__ float4 operator/(float b, float4 a) +{ + return make_float4(b / a.x, b / a.y, b / a.z, b / a.w); +} + +//////////////////////////////////////////////////////////////////////////////// +// min +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 fminf(float2 a, float2 b) +{ + return make_float2(fminf(a.x,b.x), fminf(a.y,b.y)); +} +inline __host__ __device__ float3 fminf(float3 a, float3 b) +{ + return make_float3(fminf(a.x,b.x), fminf(a.y,b.y), fminf(a.z,b.z)); +} +inline __host__ __device__ float4 fminf(float4 a, float4 b) +{ + return make_float4(fminf(a.x,b.x), fminf(a.y,b.y), fminf(a.z,b.z), fminf(a.w,b.w)); +} + +inline __host__ __device__ int2 min(int2 a, int2 b) +{ + return make_int2(min(a.x,b.x), min(a.y,b.y)); +} +inline __host__ __device__ int3 min(int3 a, int3 b) +{ + return make_int3(min(a.x,b.x), min(a.y,b.y), min(a.z,b.z)); +} +inline __host__ __device__ int4 min(int4 a, int4 b) +{ + return make_int4(min(a.x,b.x), min(a.y,b.y), min(a.z,b.z), min(a.w,b.w)); +} + +inline __host__ __device__ uint2 min(uint2 a, uint2 b) +{ + return make_uint2(min(a.x,b.x), min(a.y,b.y)); +} +inline __host__ __device__ uint3 min(uint3 a, uint3 b) +{ + return make_uint3(min(a.x,b.x), min(a.y,b.y), min(a.z,b.z)); +} +inline __host__ __device__ uint4 min(uint4 a, uint4 b) +{ + return make_uint4(min(a.x,b.x), min(a.y,b.y), min(a.z,b.z), min(a.w,b.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// max +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 fmaxf(float2 a, float2 b) +{ + return make_float2(fmaxf(a.x,b.x), fmaxf(a.y,b.y)); +} +inline __host__ __device__ float3 fmaxf(float3 a, float3 b) +{ + return make_float3(fmaxf(a.x,b.x), fmaxf(a.y,b.y), fmaxf(a.z,b.z)); +} +inline __host__ __device__ float4 fmaxf(float4 a, float4 b) +{ + return make_float4(fmaxf(a.x,b.x), fmaxf(a.y,b.y), fmaxf(a.z,b.z), fmaxf(a.w,b.w)); +} + +inline __host__ __device__ int2 max(int2 a, int2 b) +{ + return make_int2(max(a.x,b.x), max(a.y,b.y)); +} +inline __host__ __device__ int3 max(int3 a, int3 b) +{ + return make_int3(max(a.x,b.x), max(a.y,b.y), max(a.z,b.z)); +} +inline __host__ __device__ int4 max(int4 a, int4 b) +{ + return make_int4(max(a.x,b.x), max(a.y,b.y), max(a.z,b.z), max(a.w,b.w)); +} + +inline __host__ __device__ uint2 max(uint2 a, uint2 b) +{ + return make_uint2(max(a.x,b.x), max(a.y,b.y)); +} +inline __host__ __device__ uint3 max(uint3 a, uint3 b) +{ + return make_uint3(max(a.x,b.x), max(a.y,b.y), max(a.z,b.z)); +} +inline __host__ __device__ uint4 max(uint4 a, uint4 b) +{ + return make_uint4(max(a.x,b.x), max(a.y,b.y), max(a.z,b.z), max(a.w,b.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// lerp +// - linear interpolation between a and b, based on value t in [0, 1] range +//////////////////////////////////////////////////////////////////////////////// + +inline __device__ __host__ float lerp(float a, float b, float t) +{ + return a + t*(b-a); +} +inline __device__ __host__ float2 lerp(float2 a, float2 b, float t) +{ + return a + t*(b-a); +} +inline __device__ __host__ float3 lerp(float3 a, float3 b, float t) +{ + return a + t*(b-a); +} +inline __device__ __host__ float4 lerp(float4 a, float4 b, float t) +{ + return a + t*(b-a); +} + +//////////////////////////////////////////////////////////////////////////////// +// clamp +// - clamp the value v to be in the range [a, b] +//////////////////////////////////////////////////////////////////////////////// + +inline __device__ __host__ float clamp(float f, float a, float b) +{ + return fmaxf(a, fminf(f, b)); +} +inline __device__ __host__ int clamp(int f, int a, int b) +{ + return max(a, min(f, b)); +} +inline __device__ __host__ uint clamp(uint f, uint a, uint b) +{ + return max(a, min(f, b)); +} + +inline __device__ __host__ float2 clamp(float2 v, float a, float b) +{ + return make_float2(clamp(v.x, a, b), clamp(v.y, a, b)); +} +inline __device__ __host__ float2 clamp(float2 v, float2 a, float2 b) +{ + return make_float2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +inline __device__ __host__ float3 clamp(float3 v, float a, float b) +{ + return make_float3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} +inline __device__ __host__ float3 clamp(float3 v, float3 a, float3 b) +{ + return make_float3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +inline __device__ __host__ float4 clamp(float4 v, float a, float b) +{ + return make_float4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} +inline __device__ __host__ float4 clamp(float4 v, float4 a, float4 b) +{ + return make_float4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} + +inline __device__ __host__ int2 clamp(int2 v, int a, int b) +{ + return make_int2(clamp(v.x, a, b), clamp(v.y, a, b)); +} +inline __device__ __host__ int2 clamp(int2 v, int2 a, int2 b) +{ + return make_int2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +inline __device__ __host__ int3 clamp(int3 v, int a, int b) +{ + return make_int3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} +inline __device__ __host__ int3 clamp(int3 v, int3 a, int3 b) +{ + return make_int3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +inline __device__ __host__ int4 clamp(int4 v, int a, int b) +{ + return make_int4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} +inline __device__ __host__ int4 clamp(int4 v, int4 a, int4 b) +{ + return make_int4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} + +inline __device__ __host__ uint2 clamp(uint2 v, uint a, uint b) +{ + return make_uint2(clamp(v.x, a, b), clamp(v.y, a, b)); +} +inline __device__ __host__ uint2 clamp(uint2 v, uint2 a, uint2 b) +{ + return make_uint2(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y)); +} +inline __device__ __host__ uint3 clamp(uint3 v, uint a, uint b) +{ + return make_uint3(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b)); +} +inline __device__ __host__ uint3 clamp(uint3 v, uint3 a, uint3 b) +{ + return make_uint3(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z)); +} +inline __device__ __host__ uint4 clamp(uint4 v, uint a, uint b) +{ + return make_uint4(clamp(v.x, a, b), clamp(v.y, a, b), clamp(v.z, a, b), clamp(v.w, a, b)); +} +inline __device__ __host__ uint4 clamp(uint4 v, uint4 a, uint4 b) +{ + return make_uint4(clamp(v.x, a.x, b.x), clamp(v.y, a.y, b.y), clamp(v.z, a.z, b.z), clamp(v.w, a.w, b.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// dot product +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float dot(float2 a, float2 b) +{ + return a.x * b.x + a.y * b.y; +} +inline __host__ __device__ float dot(float3 a, float3 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} +inline __host__ __device__ float dot(float4 a, float4 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +inline __host__ __device__ int dot(int2 a, int2 b) +{ + return a.x * b.x + a.y * b.y; +} +inline __host__ __device__ int dot(int3 a, int3 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} +inline __host__ __device__ int dot(int4 a, int4 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +inline __host__ __device__ uint dot(uint2 a, uint2 b) +{ + return a.x * b.x + a.y * b.y; +} +inline __host__ __device__ uint dot(uint3 a, uint3 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} +inline __host__ __device__ uint dot(uint4 a, uint4 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +//////////////////////////////////////////////////////////////////////////////// +// length +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float length(float2 v) +{ + return sqrtf(dot(v, v)); +} +inline __host__ __device__ float length(float3 v) +{ + return sqrtf(dot(v, v)); +} +inline __host__ __device__ float length(float4 v) +{ + return sqrtf(dot(v, v)); +} + +//////////////////////////////////////////////////////////////////////////////// +// normalize +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 normalize(float2 v) +{ + float invLen = rsqrtf(dot(v, v)); + return v * invLen; +} +inline __host__ __device__ float3 normalize(float3 v) +{ + float invLen = rsqrtf(dot(v, v)); + return v * invLen; +} +inline __host__ __device__ float4 normalize(float4 v) +{ + float invLen = rsqrtf(dot(v, v)); + return v * invLen; +} + +//////////////////////////////////////////////////////////////////////////////// +// floor +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 floorf(float2 v) +{ + return make_float2(floorf(v.x), floorf(v.y)); +} +inline __host__ __device__ float3 floorf(float3 v) +{ + return make_float3(floorf(v.x), floorf(v.y), floorf(v.z)); +} +inline __host__ __device__ float4 floorf(float4 v) +{ + return make_float4(floorf(v.x), floorf(v.y), floorf(v.z), floorf(v.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// frac - returns the fractional portion of a scalar or each vector component +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float fracf(float v) +{ + return v - floorf(v); +} +inline __host__ __device__ float2 fracf(float2 v) +{ + return make_float2(fracf(v.x), fracf(v.y)); +} +inline __host__ __device__ float3 fracf(float3 v) +{ + return make_float3(fracf(v.x), fracf(v.y), fracf(v.z)); +} +inline __host__ __device__ float4 fracf(float4 v) +{ + return make_float4(fracf(v.x), fracf(v.y), fracf(v.z), fracf(v.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// fmod +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 fmodf(float2 a, float2 b) +{ + return make_float2(fmodf(a.x, b.x), fmodf(a.y, b.y)); +} +inline __host__ __device__ float3 fmodf(float3 a, float3 b) +{ + return make_float3(fmodf(a.x, b.x), fmodf(a.y, b.y), fmodf(a.z, b.z)); +} +inline __host__ __device__ float4 fmodf(float4 a, float4 b) +{ + return make_float4(fmodf(a.x, b.x), fmodf(a.y, b.y), fmodf(a.z, b.z), fmodf(a.w, b.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// absolute value +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float2 fabs(float2 v) +{ + return make_float2(fabs(v.x), fabs(v.y)); +} +inline __host__ __device__ float3 fabs(float3 v) +{ + return make_float3(fabs(v.x), fabs(v.y), fabs(v.z)); +} +inline __host__ __device__ float4 fabs(float4 v) +{ + return make_float4(fabs(v.x), fabs(v.y), fabs(v.z), fabs(v.w)); +} + +inline __host__ __device__ int2 abs(int2 v) +{ + return make_int2(abs(v.x), abs(v.y)); +} +inline __host__ __device__ int3 abs(int3 v) +{ + return make_int3(abs(v.x), abs(v.y), abs(v.z)); +} +inline __host__ __device__ int4 abs(int4 v) +{ + return make_int4(abs(v.x), abs(v.y), abs(v.z), abs(v.w)); +} + +//////////////////////////////////////////////////////////////////////////////// +// reflect +// - returns reflection of incident ray I around surface normal N +// - N should be normalized, reflected vector's length is equal to length of I +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float3 reflect(float3 i, float3 n) +{ + return i - 2.0f * n * dot(n,i); +} + +//////////////////////////////////////////////////////////////////////////////// +// cross product +//////////////////////////////////////////////////////////////////////////////// + +inline __host__ __device__ float3 cross(float3 a, float3 b) +{ + return make_float3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); +} + +//////////////////////////////////////////////////////////////////////////////// +// smoothstep +// - returns 0 if x < a +// - returns 1 if x > b +// - otherwise returns smooth interpolation between 0 and 1 based on x +//////////////////////////////////////////////////////////////////////////////// + +inline __device__ __host__ float smoothstep(float a, float b, float x) +{ + float y = clamp((x - a) / (b - a), 0.0f, 1.0f); + return (y*y*(3.0f - (2.0f*y))); +} +inline __device__ __host__ float2 smoothstep(float2 a, float2 b, float2 x) +{ + float2 y = clamp((x - a) / (b - a), 0.0f, 1.0f); + return (y*y*(make_float2(3.0f) - (make_float2(2.0f)*y))); +} +inline __device__ __host__ float3 smoothstep(float3 a, float3 b, float3 x) +{ + float3 y = clamp((x - a) / (b - a), 0.0f, 1.0f); + return (y*y*(make_float3(3.0f) - (make_float3(2.0f)*y))); +} +inline __device__ __host__ float4 smoothstep(float4 a, float4 b, float4 x) +{ + float4 y = clamp((x - a) / (b - a), 0.0f, 1.0f); + return (y*y*(make_float4(3.0f) - (make_float4(2.0f)*y))); +} + +#endif diff --git a/src/cuda/H100/image/recursiveGaussian/helper_string.h b/src/cuda/H100/image/recursiveGaussian/helper_string.h new file mode 100644 index 000000000..39a1b3805 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_string.h @@ -0,0 +1,428 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// These are helper functions for the SDK samples (string parsing, timers, etc) +#ifndef COMMON_HELPER_STRING_H_ +#define COMMON_HELPER_STRING_H_ + +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#ifndef STRCASECMP +#define STRCASECMP _stricmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP _strnicmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy_s(sFilePath, nLength, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) fopen_s(&fHandle, filename, mode) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result != 0) +#endif +#ifndef SSCANF +#define SSCANF sscanf_s +#endif +#ifndef SPRINTF +#define SPRINTF sprintf_s +#endif +#else // Linux Includes +#include +#include + +#ifndef STRCASECMP +#define STRCASECMP strcasecmp +#endif +#ifndef STRNCASECMP +#define STRNCASECMP strncasecmp +#endif +#ifndef STRCPY +#define STRCPY(sFilePath, nLength, sPath) strcpy(sFilePath, sPath) +#endif + +#ifndef FOPEN +#define FOPEN(fHandle, filename, mode) (fHandle = fopen(filename, mode)) +#endif +#ifndef FOPEN_FAIL +#define FOPEN_FAIL(result) (result == NULL) +#endif +#ifndef SSCANF +#define SSCANF sscanf +#endif +#ifndef SPRINTF +#define SPRINTF sprintf +#endif +#endif + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// CUDA Utility Helper Functions +inline int stringRemoveDelimiter(char delimiter, const char *string) { + int string_start = 0; + + while (string[string_start] == delimiter) { + string_start++; + } + + if (string_start >= static_cast(strlen(string) - 1)) { + return 0; + } + + return string_start; +} + +inline int getFileExtension(char *filename, char **extension) { + int string_length = static_cast(strlen(filename)); + + while (filename[string_length--] != '.') { + if (string_length == 0) break; + } + + if (string_length > 0) string_length += 2; + + if (string_length == 0) + *extension = NULL; + else + *extension = &filename[string_length]; + + return string_length; +} + +inline bool checkCmdLineFlag(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + + const char *equal_pos = strchr(string_argv, '='); + int argv_length = static_cast( + equal_pos == 0 ? strlen(string_argv) : equal_pos - string_argv); + + int length = static_cast(strlen(string_ref)); + + if (length == argv_length && + !STRNCASECMP(string_argv, string_ref, length)) { + bFound = true; + continue; + } + } + } + + return bFound; +} + +// This function wraps the CUDA Driver API into a template function +template +inline bool getCmdLineArgumentValue(const int argc, const char **argv, + const char *string_ref, T *value) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + *value = (T)atoi(&string_argv[length + auto_inc]); + } + + bFound = true; + i = argc; + } + } + } + + return bFound; +} + +inline int getCmdLineArgumentInt(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + int value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = atoi(&string_argv[length + auto_inc]); + } else { + value = 0; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline float getCmdLineArgumentFloat(const int argc, const char **argv, + const char *string_ref) { + bool bFound = false; + float value = -1; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + const char *string_argv = &argv[i][string_start]; + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + if (length + 1 <= static_cast(strlen(string_argv))) { + int auto_inc = (string_argv[length] == '=') ? 1 : 0; + value = static_cast(atof(&string_argv[length + auto_inc])); + } else { + value = 0.f; + } + + bFound = true; + continue; + } + } + } + + if (bFound) { + return value; + } else { + return 0; + } +} + +inline bool getCmdLineArgumentString(const int argc, const char **argv, + const char *string_ref, + char **string_retval) { + bool bFound = false; + + if (argc >= 1) { + for (int i = 1; i < argc; i++) { + int string_start = stringRemoveDelimiter('-', argv[i]); + char *string_argv = const_cast(&argv[i][string_start]); + int length = static_cast(strlen(string_ref)); + + if (!STRNCASECMP(string_argv, string_ref, length)) { + *string_retval = &string_argv[length + 1]; + bFound = true; + continue; + } + } + } + + if (!bFound) { + *string_retval = NULL; + } + + return bFound; +} + +////////////////////////////////////////////////////////////////////////////// +//! Find the path for a file assuming that +//! files are found in the searchPath. +//! +//! @return the path if succeeded, otherwise 0 +//! @param filename name of the file +//! @param executable_path optional absolute path of the executable +////////////////////////////////////////////////////////////////////////////// +inline char *sdkFindFilePath(const char *filename, + const char *executable_path) { + // defines a variable that is replaced with the name of the + // executable + + // Typical relative search paths to locate needed companion files (e.g. sample + // input data, or JIT source files) The origin for the relative search may be + // the .exe file, a .bat file launching an .exe, a browser .exe launching the + // .exe or .bat, etc + const char *searchPath[] = { + "./", // same dir + "./data/", // same dir + + "../../../../Samples//", // up 4 in tree + "../../../Samples//", // up 3 in tree + "../../Samples//", // up 2 in tree + + "../../../../Samples//data/", // up 4 in tree + "../../../Samples//data/", // up 3 in tree + "../../Samples//data/", // up 2 in tree + + "../../../../Samples/0_Introduction//", // up 4 in tree + "../../../Samples/0_Introduction//", // up 3 in tree + "../../Samples/0_Introduction//", // up 2 in tree + + "../../../../Samples/1_Utilities//", // up 4 in tree + "../../../Samples/1_Utilities//", // up 3 in tree + "../../Samples/1_Utilities//", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//", // up 4 in tree + "../../../Samples/3_CUDA_Features//", // up 3 in tree + "../../Samples/3_CUDA_Features//", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//", // up 3 in tree + "../../Samples/4_CUDA_Libraries//", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//", // up 4 in tree + "../../../Samples/5_Domain_Specific//", // up 3 in tree + "../../Samples/5_Domain_Specific//", // up 2 in tree + + "../../../../Samples/6_Performance//", // up 4 in tree + "../../../Samples/6_Performance//", // up 3 in tree + "../../Samples/6_Performance//", // up 2 in tree + + "../../../../Samples/0_Introduction//data/", // up 4 in tree + "../../../Samples/0_Introduction//data/", // up 3 in tree + "../../Samples/0_Introduction//data/", // up 2 in tree + + "../../../../Samples/1_Utilities//data/", // up 4 in tree + "../../../Samples/1_Utilities//data/", // up 3 in tree + "../../Samples/1_Utilities//data/", // up 2 in tree + + "../../../../Samples/2_Concepts_and_Techniques//data/", // up 4 in tree + "../../../Samples/2_Concepts_and_Techniques//data/", // up 3 in tree + "../../Samples/2_Concepts_and_Techniques//data/", // up 2 in tree + + "../../../../Samples/3_CUDA_Features//data/", // up 4 in tree + "../../../Samples/3_CUDA_Features//data/", // up 3 in tree + "../../Samples/3_CUDA_Features//data/", // up 2 in tree + + "../../../../Samples/4_CUDA_Libraries//data/", // up 4 in tree + "../../../Samples/4_CUDA_Libraries//data/", // up 3 in tree + "../../Samples/4_CUDA_Libraries//data/", // up 2 in tree + + "../../../../Samples/5_Domain_Specific//data/", // up 4 in tree + "../../../Samples/5_Domain_Specific//data/", // up 3 in tree + "../../Samples/5_Domain_Specific//data/", // up 2 in tree + + "../../../../Samples/6_Performance//data/", // up 4 in tree + "../../../Samples/6_Performance//data/", // up 3 in tree + "../../Samples/6_Performance//data/", // up 2 in tree + + "../../../../Common/data/", // up 4 in tree + "../../../Common/data/", // up 3 in tree + "../../Common/data/" // up 2 in tree + }; + + // Extract the executable name + std::string executable_name; + + if (executable_path != 0) { + executable_name = std::string(executable_path); + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + // Windows path delimiter + size_t delimiter_pos = executable_name.find_last_of('\\'); + executable_name.erase(0, delimiter_pos + 1); + + if (executable_name.rfind(".exe") != std::string::npos) { + // we strip .exe, only if the .exe is found + executable_name.resize(executable_name.size() - 4); + } + +#else + // Linux & OSX path delimiter + size_t delimiter_pos = executable_name.find_last_of('/'); + executable_name.erase(0, delimiter_pos + 1); +#endif + } + + // Loop over all search paths and return the first hit + for (unsigned int i = 0; i < sizeof(searchPath) / sizeof(char *); ++i) { + std::string path(searchPath[i]); + size_t executable_name_pos = path.find(""); + + // If there is executable_name variable in the searchPath + // replace it with the value + if (executable_name_pos != std::string::npos) { + if (executable_path != 0) { + path.replace(executable_name_pos, strlen(""), + executable_name); + } else { + // Skip this path entry if no executable argument is given + continue; + } + } + +#ifdef _DEBUG + printf("sdkFindFilePath <%s> in %s\n", filename, path.c_str()); +#endif + + // Test if the file exists + path.append(filename); + FILE *fp; + FOPEN(fp, path.c_str(), "rb"); + + if (fp != NULL) { + fclose(fp); + // File found + // returning an allocated array here for backwards compatibility reasons + char *file_path = reinterpret_cast(malloc(path.length() + 1)); + STRCPY(file_path, path.length() + 1, path.c_str()); + return file_path; + } + + if (fp) { + fclose(fp); + } + } + + // File not found + printf("\nerror: sdkFindFilePath: file <%s> not found!\n", filename); + return 0; +} + +#endif // COMMON_HELPER_STRING_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/helper_timer.h b/src/cuda/H100/image/recursiveGaussian/helper_timer.h new file mode 100644 index 000000000..0614a7802 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/helper_timer.h @@ -0,0 +1,465 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Helper Timing Functions +#ifndef COMMON_HELPER_TIMER_H_ +#define COMMON_HELPER_TIMER_H_ + +#ifndef EXIT_WAIVED +#define EXIT_WAIVED 2 +#endif + +// includes, system +#include + +// includes, project +#include "exception.h" + +// Definition of the StopWatch Interface, this is used if we don't want to use +// the CUT functions But rather in a self contained class interface +class StopWatchInterface { + public: + StopWatchInterface() {} + virtual ~StopWatchInterface() {} + + public: + //! Start time measurement + virtual void start() = 0; + + //! Stop time measurement + virtual void stop() = 0; + + //! Reset time counters to zero + virtual void reset() = 0; + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + virtual float getTime() = 0; + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + virtual float getAverageTime() = 0; +}; + +////////////////////////////////////////////////////////////////// +// Begin Stopwatch timer class definitions for all OS platforms // +////////////////////////////////////////////////////////////////// +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +// includes, system +#define WINDOWS_LEAN_AND_MEAN +#include +#undef min +#undef max + +//! Windows specific implementation of StopWatch +class StopWatchWin : public StopWatchInterface { + public: + //! Constructor, default + StopWatchWin() + : start_time(), + end_time(), + diff_time(0.0f), + total_time(0.0f), + running(false), + clock_sessions(0), + freq(0), + freq_set(false) { + if (!freq_set) { + // helper variable + LARGE_INTEGER temp; + + // get the tick frequency from the OS + QueryPerformanceFrequency(reinterpret_cast(&temp)); + + // convert to type in which it is needed + freq = (static_cast(temp.QuadPart)) / 1000.0; + + // rememeber query + freq_set = true; + } + } + + // Destructor + ~StopWatchWin() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // member variables + + //! Start of measurement + LARGE_INTEGER start_time; + //! End of measurement + LARGE_INTEGER end_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; + + //! tick frequency + double freq; + + //! flag if the frequency has been set + bool freq_set; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::start() { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::stop() { + QueryPerformanceCounter(reinterpret_cast(&end_time)); + diff_time = static_cast(((static_cast(end_time.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + + total_time += diff_time; + clock_sessions++; + running = false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchWin::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + QueryPerformanceCounter(reinterpret_cast(&start_time)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + LARGE_INTEGER temp; + QueryPerformanceCounter(reinterpret_cast(&temp)); + retval += static_cast(((static_cast(temp.QuadPart) - + static_cast(start_time.QuadPart)) / + freq)); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchWin::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +#else +// Declarations for Stopwatch on Linux and Mac OSX +// includes, system +#include +#include + +//! Windows specific implementation of StopWatch +class StopWatchLinux : public StopWatchInterface { + public: + //! Constructor, default + StopWatchLinux() + : start_time(), + diff_time(0.0), + total_time(0.0), + running(false), + clock_sessions(0) {} + + // Destructor + virtual ~StopWatchLinux() {} + + public: + //! Start time measurement + inline void start(); + + //! Stop time measurement + inline void stop(); + + //! Reset time counters to zero + inline void reset(); + + //! Time in msec. after start. If the stop watch is still running (i.e. there + //! was no call to stop()) then the elapsed time is returned, otherwise the + //! time between the last start() and stop call is returned + inline float getTime(); + + //! Mean time to date based on the number of times the stopwatch has been + //! _stopped_ (ie finished sessions) and the current total time + inline float getAverageTime(); + + private: + // helper functions + + //! Get difference between start time and current time + inline float getDiffTime(); + + private: + // member variables + + //! Start of measurement + struct timeval start_time; + + //! Time difference between the last start and stop + float diff_time; + + //! TOTAL time difference between starts and stops + float total_time; + + //! flag if the stop watch is running + bool running; + + //! Number of times clock has been started + //! and stopped to allow averaging + int clock_sessions; +}; + +// functions, inlined + +//////////////////////////////////////////////////////////////////////////////// +//! Start time measurement +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::start() { + gettimeofday(&start_time, 0); + running = true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop time measurement and increment add to the current diff_time summation +//! variable. Also increment the number of times this clock has been run. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::stop() { + diff_time = getDiffTime(); + total_time += diff_time; + running = false; + clock_sessions++; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Reset the timer to 0. Does not change the timer running state but does +//! recapture this point in time as the current start time if it is running. +//////////////////////////////////////////////////////////////////////////////// +inline void StopWatchLinux::reset() { + diff_time = 0; + total_time = 0; + clock_sessions = 0; + + if (running) { + gettimeofday(&start_time, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. after start. If the stop watch is still running (i.e. there +//! was no call to stop()) then the elapsed time is returned added to the +//! current diff_time sum, otherwise the current summed time difference alone +//! is returned. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getTime() { + // Return the TOTAL time to date + float retval = total_time; + + if (running) { + retval += getDiffTime(); + } + + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Time in msec. for a single run based on the total number of COMPLETED runs +//! and the total time. +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getAverageTime() { + return (clock_sessions > 0) ? (total_time / clock_sessions) : 0.0f; +} +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +inline float StopWatchLinux::getDiffTime() { + struct timeval t_time; + gettimeofday(&t_time, 0); + + // time difference in milli-seconds + return static_cast(1000.0 * (t_time.tv_sec - start_time.tv_sec) + + (0.001 * (t_time.tv_usec - start_time.tv_usec))); +} +#endif // WIN32 + +//////////////////////////////////////////////////////////////////////////////// +//! Timer functionality exported + +//////////////////////////////////////////////////////////////////////////////// +//! Create a new timer +//! @return true if a time has been created, otherwise false +//! @param name of the new timer, 0 if the creation failed +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkCreateTimer(StopWatchInterface **timer_interface) { +// printf("sdkCreateTimer called object %08x\n", (void *)*timer_interface); +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + *timer_interface = reinterpret_cast(new StopWatchWin()); +#else + *timer_interface = + reinterpret_cast(new StopWatchLinux()); +#endif + return (*timer_interface != NULL) ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Delete a timer +//! @return true if a time has been deleted, otherwise false +//! @param name of the timer to delete +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkDeleteTimer(StopWatchInterface **timer_interface) { + // printf("sdkDeleteTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + delete *timer_interface; + *timer_interface = NULL; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Start the time with name \a name +//! @param name name of the timer to start +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStartTimer(StopWatchInterface **timer_interface) { + // printf("sdkStartTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->start(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Stop the time with name \a name. Does not reset. +//! @param name name of the timer to stop +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkStopTimer(StopWatchInterface **timer_interface) { + // printf("sdkStopTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->stop(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Resets the timer's counter. +//! @param name name of the timer to reset. +//////////////////////////////////////////////////////////////////////////////// +inline bool sdkResetTimer(StopWatchInterface **timer_interface) { + // printf("sdkResetTimer called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + (*timer_interface)->reset(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Return the average time for timer execution as the total time +//! for the timer dividied by the number of completed (stopped) runs the timer +//! has made. +//! Excludes the current running time if the timer is currently running. +//! @param name name of the timer to return the time of +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetAverageTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetAverageTimerValue called object %08x\n", (void + // *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getAverageTime(); + } else { + return 0.0f; + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! Total execution time for the timer over all runs since the last reset +//! or timer creation. +//! @param name name of the timer to obtain the value of. +//////////////////////////////////////////////////////////////////////////////// +inline float sdkGetTimerValue(StopWatchInterface **timer_interface) { + // printf("sdkGetTimerValue called object %08x\n", (void *)*timer_interface); + if (*timer_interface) { + return (*timer_interface)->getTime(); + } else { + return 0.0f; + } +} + +#endif // COMMON_HELPER_TIMER_H_ diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp new file mode 100644 index 000000000..1a6172e0d --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp @@ -0,0 +1,587 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Recursive Gaussian filter + sgreen 8/1/08 + + This code sample implements a Gaussian blur using Deriche's recursive method: + http://citeseer.ist.psu.edu/deriche93recursively.html + + This is similar to the box filter sample in the SDK, but it uses the previous + outputs of the filter as well as the previous inputs. This is also known as an + IIR (infinite impulse response) filter, since its response to an input impulse + can last forever. + + The main advantage of this method is that the execution time is independent of + the filter width. + + The GPU processes columns of the image in parallel. To avoid uncoalesced reads + for the row pass we transpose the image and then transpose it back again + afterwards. + + The implementation is based on code from the CImg library: + http://cimg.sourceforge.net/ + Thanks to David Tschumperl� and all the CImg contributors! +*/ + +#pragma warning(disable : 4819) + +#ifndef NO_OPENGL +// OpenGL Graphics includes +#include "helper_gl.h" +#if defined(__APPLE__) || defined(MACOSX) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#include +#ifndef glutCloseFunc +#define glutCloseFunc glutWMCloseFunc +#endif +#else +#include +#endif + +// CUDA includes and interop headers +#include +#endif + +#include + +// CUDA utilities and system includes +#include "helper_cuda.h" // includes cuda.h and cuda_runtime_api.h +#include "helper_functions.h" + +// Includes +#include +#include +#include +#include + +#define MAX(a, b) ((a > b) ? a : b) + +#define USE_SIMPLE_FILTER 0 + +#define MAX_EPSILON_ERROR 5.0f +#define THRESHOLD 0.15f + +// Define the files that are to be save and the reference images for validation +const char *sOriginal[] = {"teapot512_10.ppm", "teapot512_14.ppm", "teapot512_18.ppm", "teapot512_22.ppm", NULL}; + +const char *sReference[] = {"ref_10.ppm", "ref_14.ppm", "ref_18.ppm", "ref_22.ppm", NULL}; + +const char *image_filename = "teapot512.ppm"; +float sigma = 10.0f; +int order = 0; +int nthreads = 64; // number of threads per block + +unsigned int width, height; +unsigned int *h_img = NULL; +unsigned int *d_img = NULL; +unsigned int *d_temp = NULL; + +#ifndef NO_OPENGL +GLuint pbo = 0; // OpenGL pixel buffer object +GLuint texid = 0; // texture + +cudaGraphicsResource_t cuda_vbo_resource; +#endif + +StopWatchInterface *timer = 0; + +// Auto-Verification Code +const int frameCheckNumber = 4; +int fpsCount = 0; // FPS count for averaging +int fpsLimit = 1; // FPS limit for sampling +unsigned int frameCount = 0; + +int *pArgc = NULL; +char **pArgv = NULL; + +bool runBenchmark = false; + +const char *sSDKsample = "CUDA Recursive Gaussian"; + +extern "C" void transpose(unsigned int *d_src, unsigned int *d_dest, unsigned int width, int height); + +extern "C" void gaussianFilterRGBA(unsigned int *d_src, + unsigned int *d_dest, + unsigned int *d_temp, + int width, + int height, + float sigma, + int order, + int nthreads); + +void cleanup(); + +#ifndef NO_OPENGL +void computeFPS() +{ + frameCount++; + fpsCount++; + + if (fpsCount == fpsLimit) { + char fps[256]; + float ifps = 1.f / (sdkGetAverageTimerValue(&timer) / 1000.f); + sprintf(fps, "%s (sigma=%4.2f): %3.1f fps", sSDKsample, sigma, ifps); + + glutSetWindowTitle(fps); + fpsCount = 0; + + fpsLimit = ftoi(MAX(ifps, 1.f)); + sdkResetTimer(&timer); + } +} + +// display results using OpenGL +void display() +{ + sdkStartTimer(&timer); + + // execute filter, writing results to pbo + unsigned int *d_result; + checkCudaErrors(cudaGraphicsMapResources(1, &cuda_vbo_resource, 0)); + size_t num_bytes; + checkCudaErrors(cudaGraphicsResourceGetMappedPointer((void **)&d_result, &num_bytes, cuda_vbo_resource)); + gaussianFilterRGBA(d_img, d_result, d_temp, width, height, sigma, order, nthreads); + + // unmap buffer object + checkCudaErrors(cudaGraphicsUnmapResources(1, &cuda_vbo_resource, 0)); + + // load texture from pbo + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo); + glBindTexture(GL_TEXTURE_2D, texid); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + + // display results + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + + glBegin(GL_QUADS); + glTexCoord2f(0, 1); + glVertex2f(0, 0); + glTexCoord2f(1, 1); + glVertex2f(1, 0); + glTexCoord2f(1, 0); + glVertex2f(1, 1); + glTexCoord2f(0, 0); + glVertex2f(0, 1); + glEnd(); + + glDisable(GL_TEXTURE_2D); + glutSwapBuffers(); + + sdkStopTimer(&timer); + + computeFPS(); +} + +void idle() { glutPostRedisplay(); } +#endif + +void cleanup() +{ + sdkDeleteTimer(&timer); + + checkCudaErrors(cudaFree(d_img)); + checkCudaErrors(cudaFree(d_temp)); + +#ifndef NO_OPENGL + if (!runBenchmark) { + if (pbo) { + // unregister this buffer object with CUDA + checkCudaErrors(cudaGraphicsUnregisterResource(cuda_vbo_resource)); + glDeleteBuffers(1, &pbo); + } + + if (texid) { + glDeleteTextures(1, &texid); + } + } +#endif +} + +#ifndef NO_OPENGL +void keyboard(unsigned char key, int x, int y) +{ + switch (key) { + case 27: +#if defined(__APPLE__) || defined(MACOSX) + exit(EXIT_SUCCESS); +#else + glutDestroyWindow(glutGetWindow()); + return; +#endif + break; + + case '=': + sigma += 0.1f; + break; + + case '-': + sigma -= 0.1f; + + if (sigma < 0.0) { + sigma = 0.0f; + } + + break; + + case '+': + sigma += 1.0f; + break; + + case '_': + sigma -= 1.0f; + + if (sigma < 0.0) { + sigma = 0.0f; + } + + break; + + case '0': + order = 0; + break; + + case '1': + order = 1; + sigma = 0.5f; + break; + + case '2': + order = 2; + sigma = 0.5f; + break; + + default: + break; + } + + printf("sigma = %f\n", sigma); + glutPostRedisplay(); +} + +void reshape(int x, int y) +{ + glViewport(0, 0, x, y); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0); +} + +void initGLBuffers() +{ + // create pixel buffer object to store final image + glGenBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * sizeof(GLubyte) * 4, h_img, GL_STREAM_DRAW_ARB); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + checkCudaErrors(cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, pbo, cudaGraphicsRegisterFlagsWriteDiscard)); + + // create texture for display + glGenTextures(1, &texid); + glBindTexture(GL_TEXTURE_2D, texid); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void initGL(int *argc, char **argv) +{ + glutInit(argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); + glutInitWindowSize(width, height); + glutCreateWindow(sSDKsample); + glutDisplayFunc(display); + glutKeyboardFunc(keyboard); + glutReshapeFunc(reshape); + glutIdleFunc(idle); + +#if defined(__APPLE__) || defined(MACOSX) + atexit(cleanup); +#else + glutCloseFunc(cleanup); +#endif + + printf("Press '+' and '-' to change filter width\n"); + printf("0, 1, 2 - change filter order\n"); + + if (!isGLVersionSupported(2, 0) + || !areGLExtensionsSupported("GL_ARB_vertex_buffer_object GL_ARB_pixel_buffer_object")) { + fprintf(stderr, "Required OpenGL extensions missing."); + exit(EXIT_FAILURE); + } +} +#endif + +void initCudaBuffers() +{ + unsigned int size = width * height * sizeof(unsigned int); + + // allocate device memory + checkCudaErrors(cudaMalloc((void **)&d_img, size)); + checkCudaErrors(cudaMalloc((void **)&d_temp, size)); + + checkCudaErrors(cudaMemcpy(d_img, h_img, size, cudaMemcpyHostToDevice)); + + sdkCreateTimer(&timer); +} + +void benchmark(int iterations) +{ + // allocate memory for result + unsigned int *d_result; + unsigned int size = width * height * sizeof(unsigned int); + checkCudaErrors(cudaMalloc((void **)&d_result, size)); + + // warm-up + gaussianFilterRGBA(d_img, d_result, d_temp, width, height, sigma, order, nthreads); + + checkCudaErrors(cudaDeviceSynchronize()); + sdkStartTimer(&timer); + + // execute the kernel + for (int i = 0; i < iterations; i++) { + gaussianFilterRGBA(d_img, d_result, d_temp, width, height, sigma, order, nthreads); + } + + checkCudaErrors(cudaDeviceSynchronize()); + sdkStopTimer(&timer); + + // check if kernel execution generated an error + getLastCudaError("Kernel execution failed"); + + printf("Processing time: %f (ms)\n", sdkGetTimerValue(&timer)); + printf("%.2f Mpixels/sec\n", (width * height * iterations / (sdkGetTimerValue(&timer) / 1000.0f)) / 1e6); + + checkCudaErrors(cudaFree(d_result)); +} + +bool runSingleTest(const char *ref_file, const char *exec_path) +{ + // allocate memory for result + int nTotalErrors = 0; + unsigned int *d_result; + unsigned int size = width * height * sizeof(unsigned int); + checkCudaErrors(cudaMalloc((void **)&d_result, size)); + + // warm-up + gaussianFilterRGBA(d_img, d_result, d_temp, width, height, sigma, order, nthreads); + + checkCudaErrors(cudaDeviceSynchronize()); + sdkStartTimer(&timer); + + gaussianFilterRGBA(d_img, d_result, d_temp, width, height, sigma, order, nthreads); + checkCudaErrors(cudaDeviceSynchronize()); + getLastCudaError("Kernel execution failed"); + sdkStopTimer(&timer); + + unsigned char *h_result = (unsigned char *)malloc(width * height * 4); + checkCudaErrors(cudaMemcpy(h_result, d_result, width * height * 4, cudaMemcpyDeviceToHost)); + + char dump_file[1024]; + sprintf(dump_file, "teapot512_%02d.ppm", (int)sigma); + sdkSavePPM4ub(dump_file, h_result, width, height); + + if (!sdkComparePPM(dump_file, sdkFindFilePath(ref_file, exec_path), MAX_EPSILON_ERROR, THRESHOLD, false)) { + nTotalErrors++; + } + + printf("Processing time: %f (ms)\n", sdkGetTimerValue(&timer)); + printf("%.2f Mpixels/sec\n", (width * height / (sdkGetTimerValue(&timer) / 1000.0f)) / 1e6); + + checkCudaErrors(cudaFree(d_result)); + free(h_result); + + printf("Summary: %d errors!\n", nTotalErrors); + + printf(nTotalErrors == 0 ? "Test passed\n" : "Test failed!\n"); + return (nTotalErrors == 0); +} + +//////////////////////////////////////////////////////////////////////////////// +// Program main +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + pArgc = &argc; + pArgv = argv; + char *ref_file = NULL; + +#if defined(__linux__) + setenv("DISPLAY", ":0", 0); +#endif + + printf("%s Starting...\n\n", sSDKsample); + + printf("NOTE: The CUDA Samples are not meant for performance measurements. " + "Results may vary when GPU Boost is enabled.\n\n"); + + // use command-line specified CUDA device, otherwise use device with highest + // Gflops/s + if (argc > 1) { + if (checkCmdLineFlag(argc, (const char **)argv, "file")) { + getCmdLineArgumentString(argc, (const char **)argv, "file", &ref_file); + fpsLimit = frameCheckNumber; + } + } + + // Get the path of the filename + char *filename; + + if (getCmdLineArgumentString(argc, (const char **)argv, "image", &filename)) { + image_filename = filename; + } + + // load image + char *image_path = sdkFindFilePath(image_filename, argv[0]); + + if (image_path == NULL) { + fprintf(stderr, "Error unable to find and load image file: '%s'\n", image_filename); + exit(EXIT_FAILURE); + } + + sdkLoadPPM4ub(image_path, (unsigned char **)&h_img, &width, &height); + + if (!h_img) { + printf("Error unable to load PPM file: '%s'\n", image_path); + exit(EXIT_FAILURE); + } + + printf("Loaded '%s', %d x %d pixels\n", image_path, width, height); + + if (checkCmdLineFlag(argc, (const char **)argv, "threads")) { + nthreads = getCmdLineArgumentInt(argc, (const char **)argv, "threads"); + } + + if (checkCmdLineFlag(argc, (const char **)argv, "sigma")) { + sigma = getCmdLineArgumentFloat(argc, (const char **)argv, "sigma"); + } + + int iterations = 100; // Default iterations + if (checkCmdLineFlag(argc, (const char **)argv, "iterations")) { + iterations = getCmdLineArgumentInt(argc, (const char **)argv, "iterations"); + } + + runBenchmark = checkCmdLineFlag(argc, (const char **)argv, "benchmark"); + +#ifdef NO_OPENGL + // Check if benchmark mode is enabled when compiled without OpenGL + if (!runBenchmark) { + fprintf(stderr, "\n"); + fprintf(stderr, "========================================\n"); + fprintf(stderr, "WARNING: This binary was compiled without OpenGL support.\n"); + fprintf(stderr, "You must use the --benchmark flag to run this program.\n"); + fprintf(stderr, "========================================\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Auto-enabling benchmark mode...\n"); + fprintf(stderr, "\n"); + runBenchmark = true; + } +#endif + + int device; + struct cudaDeviceProp prop; + cudaGetDevice(&device); + cudaGetDeviceProperties(&prop, device); + + if (!strncmp("Tesla", prop.name, 5)) { + printf("Tesla card detected, running the test in benchmark mode (no OpenGL " + "display)\n"); + // runBenchmark = true; + runBenchmark = true; + } + + // Benchmark or AutoTest mode detected, no OpenGL + if (runBenchmark == true || ref_file != NULL) { + findCudaDevice(argc, (const char **)argv); + } +#ifndef NO_OPENGL + else { + // First initialize OpenGL context, and then select CUDA device. + initGL(&argc, argv); + findCudaDevice(argc, (const char **)argv); + } +#else + else { + // This binary was compiled without OpenGL support + fprintf(stderr, "\n"); + fprintf(stderr, "ERROR: This binary was compiled without OpenGL support (NO_OPENGL defined).\n"); + fprintf(stderr, "You must run in benchmark mode using the --benchmark flag.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s --benchmark [--iterations N] [--image FILE]\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "Example: %s --benchmark --iterations 1\n", argv[0]); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); + } +#endif + + initCudaBuffers(); + + if (ref_file) { + printf("(Automated Testing)\n"); + bool testPassed = runSingleTest(ref_file, argv[0]); + + cleanup(); + exit(testPassed ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if (runBenchmark) { + printf("(Run Benchmark with %d iteration%s)\n", iterations, iterations == 1 ? "" : "s"); + benchmark(iterations); + + cleanup(); + exit(EXIT_SUCCESS); + } + +#ifndef NO_OPENGL + initGLBuffers(); + glutMainLoop(); +#else + // If we get here, something went wrong - we're not in benchmark mode + // but OpenGL is disabled + fprintf(stderr, "\n"); + fprintf(stderr, "ERROR: Reached interactive mode path, but this binary was compiled without OpenGL.\n"); + fprintf(stderr, "This should not happen. Please use --benchmark flag.\n"); + fprintf(stderr, "\n"); + cleanup(); + exit(EXIT_FAILURE); +#endif + + exit(EXIT_SUCCESS); +} diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu new file mode 100644 index 000000000..353c30019 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu @@ -0,0 +1,155 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Recursive Gaussian filter + sgreen 8/1/08 + + This code sample implements a Gaussian blur using Deriche's recursive method: + http://citeseer.ist.psu.edu/deriche93recursively.html + + This is similar to the box filter sample in the SDK, but it uses the previous + outputs of the filter as well as the previous inputs. This is also known as an + IIR (infinite impulse response) filter, since its response to an input impulse + can last forever. + + The main advantage of this method is that the execution time is independent of + the filter width. + + The GPU processes columns of the image in parallel. To avoid uncoalesced reads + for the row pass we transpose the image and then transpose it back again + afterwards. + + The implementation is based on code from the CImg library: + http://cimg.sourceforge.net/ + Thanks to David Tschumperl� and all the CImg contributors! +*/ + +#include +#include "helper_cuda.h" +#include "helper_math.h" +#include +#include +#include + +#include "recursiveGaussian_kernel.cuh" + +#define USE_SIMPLE_FILTER 0 + +// Round a / b to nearest higher integer value +int iDivUp(int a, int b) { return (a % b != 0) ? (a / b + 1) : (a / b); } + +/* + Transpose a 2D array (see SDK transpose example) +*/ +extern "C" void transpose(uint *d_src, uint *d_dest, uint width, int height) +{ + dim3 grid(iDivUp(width, BLOCK_DIM), iDivUp(height, BLOCK_DIM), 1); + dim3 threads(BLOCK_DIM, BLOCK_DIM, 1); + d_transpose<<>>(d_dest, d_src, width, height); + getLastCudaError("Kernel execution failed"); +} + +/* + Perform Gaussian filter on a 2D image using CUDA + + Parameters: + d_src - pointer to input image in device memory + d_dest - pointer to destination image in device memory + d_temp - pointer to temporary storage in device memory + width - image width + height - image height + sigma - sigma of Gaussian + order - filter order (0, 1 or 2) +*/ + +// 8-bit RGBA version +extern "C" void +gaussianFilterRGBA(uint *d_src, uint *d_dest, uint *d_temp, int width, int height, float sigma, int order, int nthreads) +{ + // compute filter coefficients + const float nsigma = sigma < 0.1f ? 0.1f : sigma, alpha = 1.695f / nsigma, ema = (float)std::exp(-alpha), + ema2 = (float)std::exp(-2 * alpha), b1 = -2 * ema, b2 = ema2; + + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + + switch (order) { + case 0: { + const float k = (1 - ema) * (1 - ema) / (1 + 2 * alpha * ema - ema2); + a0 = k; + a1 = k * (alpha - 1) * ema; + a2 = k * (alpha + 1) * ema; + a3 = -k * ema2; + } break; + + case 1: { + const float k = (1 - ema) * (1 - ema) / ema; + a0 = k * ema; + a1 = a3 = 0; + a2 = -a0; + } break; + + case 2: { + const float ea = (float)std::exp(-alpha), k = -(ema2 - 1) / (2 * alpha * ema), + kn = (-2 * (-1 + 3 * ea - 3 * ea * ea + ea * ea * ea) / (3 * ea + 1 + 3 * ea * ea + ea * ea * ea)); + a0 = kn; + a1 = -kn * (1 + k * alpha) * ema; + a2 = kn * (1 - k * alpha) * ema; + a3 = -kn * ema2; + } break; + + default: + fprintf(stderr, "gaussianFilter: invalid order parameter!\n"); + return; + } + + coefp = (a0 + a1) / (1 + b1 + b2); + coefn = (a2 + a3) / (1 + b1 + b2); + +// process columns +#if USE_SIMPLE_FILTER + d_simpleRecursive_rgba<<>>(d_src, d_temp, width, height, ema); +#else + d_recursiveGaussian_rgba<<>>( + d_src, d_temp, width, height, a0, a1, a2, a3, b1, b2, coefp, coefn); +#endif + getLastCudaError("Kernel execution failed"); + + transpose(d_temp, d_dest, width, height); + getLastCudaError("transpose: Kernel execution failed"); + +// process rows +#if USE_SIMPLE_FILTER + d_simpleRecursive_rgba<<>>(d_dest, d_temp, height, width, ema); +#else + d_recursiveGaussian_rgba<<>>( + d_dest, d_temp, height, width, a0, a1, a2, a3, b1, b2, coefp, coefn); +#endif + getLastCudaError("Kernel execution failed"); + + transpose(d_temp, d_dest, height, width); +} diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh new file mode 100644 index 000000000..4db2642c9 --- /dev/null +++ b/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh @@ -0,0 +1,235 @@ +/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Recursive Gaussian filter +*/ + +#ifndef _RECURSIVEGAUSSIAN_KERNEL_CU_ +#define _RECURSIVEGAUSSIAN_KERNEL_CU_ + +#include +#include +#include +#include + +namespace cg = cooperative_groups; + +#include "helper_cuda.h" +#include "helper_math.h" + +#define BLOCK_DIM 16 +#define CLAMP_TO_EDGE 1 + +// Transpose kernel (see transpose CUDA Sample for details) +__global__ void d_transpose(uint *odata, uint *idata, int width, int height) +{ + // Handle to thread block group + cg::thread_block cta = cg::this_thread_block(); + + __shared__ uint block[BLOCK_DIM][BLOCK_DIM + 1]; + + // read the matrix tile into shared memory + unsigned int xIndex = blockIdx.x * BLOCK_DIM + threadIdx.x; + unsigned int yIndex = blockIdx.y * BLOCK_DIM + threadIdx.y; + + if ((xIndex < width) && (yIndex < height)) { + unsigned int index_in = yIndex * width + xIndex; + block[threadIdx.y][threadIdx.x] = idata[index_in]; + } + + cg::sync(cta); + + // write the transposed matrix tile to global memory + xIndex = blockIdx.y * BLOCK_DIM + threadIdx.x; + yIndex = blockIdx.x * BLOCK_DIM + threadIdx.y; + + if ((xIndex < height) && (yIndex < width)) { + unsigned int index_out = yIndex * height + xIndex; + odata[index_out] = block[threadIdx.x][threadIdx.y]; + } +} + +// RGBA version +// reads from 32-bit uint array holding 8-bit RGBA + +// convert floating point rgba color to 32-bit integer +__device__ uint rgbaFloatToInt(float4 rgba) +{ + rgba.x = __saturatef(rgba.x); // clamp to [0.0, 1.0] + rgba.y = __saturatef(rgba.y); + rgba.z = __saturatef(rgba.z); + rgba.w = __saturatef(rgba.w); + return (uint(rgba.w * 255) << 24) | (uint(rgba.z * 255) << 16) | (uint(rgba.y * 255) << 8) | uint(rgba.x * 255); +} + +// convert from 32-bit int to float4 +__device__ float4 rgbaIntToFloat(uint c) +{ + float4 rgba; + rgba.x = (c & 0xff) / 255.0f; + rgba.y = ((c >> 8) & 0xff) / 255.0f; + rgba.z = ((c >> 16) & 0xff) / 255.0f; + rgba.w = ((c >> 24) & 0xff) / 255.0f; + return rgba; +} + +/* + simple 1st order recursive filter + - processes one image column per thread + + parameters: + id - pointer to input data (RGBA image packed into 32-bit integers) + od - pointer to output data + w - image width + h - image height + a - blur parameter +*/ + +__global__ void d_simpleRecursive_rgba(uint *id, uint *od, int w, int h, float a) +{ + unsigned int x = blockIdx.x * blockDim.x + threadIdx.x; + + if (x >= w) + return; + + id += x; // advance pointers to correct column + od += x; + + // forward pass + float4 yp = rgbaIntToFloat(*id); // previous output + + for (int y = 0; y < h; y++) { + float4 xc = rgbaIntToFloat(*id); + float4 yc = xc + a * (yp - xc); // simple lerp between current and previous value + *od = rgbaFloatToInt(yc); + id += w; + od += w; // move to next row + yp = yc; + } + + // reset pointers to point to last element in column + id -= w; + od -= w; + + // reverse pass + // ensures response is symmetrical + yp = rgbaIntToFloat(*id); + + for (int y = h - 1; y >= 0; y--) { + float4 xc = rgbaIntToFloat(*id); + float4 yc = xc + a * (yp - xc); + *od = rgbaFloatToInt((rgbaIntToFloat(*od) + yc) * 0.5f); + id -= w; + od -= w; // move to previous row + yp = yc; + } +} + +/* + recursive Gaussian filter + + parameters: + id - pointer to input data (RGBA image packed into 32-bit integers) + od - pointer to output data + w - image width + h - image height + a0-a3, b1, b2, coefp, coefn - filter parameters +*/ + +__global__ void d_recursiveGaussian_rgba(uint *id, + uint *od, + int w, + int h, + float a0, + float a1, + float a2, + float a3, + float b1, + float b2, + float coefp, + float coefn) +{ + unsigned int x = blockIdx.x * blockDim.x + threadIdx.x; + + if (x >= w) + return; + + id += x; // advance pointers to correct column + od += x; + + // forward pass + float4 xp = make_float4(0.0f); // previous input + float4 yp = make_float4(0.0f); // previous output + float4 yb = make_float4(0.0f); // previous output by 2 +#if CLAMP_TO_EDGE + xp = rgbaIntToFloat(*id); + yb = coefp * xp; + yp = yb; +#endif + + for (int y = 0; y < h; y++) { + float4 xc = rgbaIntToFloat(*id); + float4 yc = a0 * xc + a1 * xp - b1 * yp - b2 * yb; + *od = rgbaFloatToInt(yc); + id += w; + od += w; // move to next row + xp = xc; + yb = yp; + yp = yc; + } + + // reset pointers to point to last element in column + id -= w; + od -= w; + + // reverse pass + // ensures response is symmetrical + float4 xn = make_float4(0.0f); + float4 xa = make_float4(0.0f); + float4 yn = make_float4(0.0f); + float4 ya = make_float4(0.0f); +#if CLAMP_TO_EDGE + xn = xa = rgbaIntToFloat(*id); + yn = coefn * xn; + ya = yn; +#endif + + for (int y = h - 1; y >= 0; y--) { + float4 xc = rgbaIntToFloat(*id); + float4 yc = a2 * xn + a3 * xa - b1 * yn - b2 * ya; + xa = xn; + xn = xc; + ya = yn; + yn = yc; + *od = rgbaFloatToInt(rgbaIntToFloat(*od) + yc); + id -= w; + od -= w; // move to previous row + } +} + +#endif // #ifndef _GAUSSIAN_KERNEL_H_ diff --git a/src/cuda/H100/newton/newton_diffsim_ball b/src/cuda/H100/newton/newton_diffsim_ball new file mode 100755 index 000000000..7d501253b --- /dev/null +++ b/src/cuda/H100/newton/newton_diffsim_ball @@ -0,0 +1,8 @@ +#!/bin/bash +# Wrapper for Newton diffsim ball benchmark + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setup_newton.sh" + +# Run Newton example +python -m newton.examples.diffsim.example_diffsim_ball --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/newton_mpm_granular b/src/cuda/H100/newton/newton_mpm_granular new file mode 100755 index 000000000..ae8cbce57 --- /dev/null +++ b/src/cuda/H100/newton/newton_mpm_granular @@ -0,0 +1,8 @@ +#!/bin/bash +# Wrapper for Newton MPM granular benchmark + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setup_newton.sh" + +# Run Newton example +python -m newton.examples.mpm.example_mpm_granular --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/newton_robot_cartpole b/src/cuda/H100/newton/newton_robot_cartpole new file mode 100755 index 000000000..73c6dd9f4 --- /dev/null +++ b/src/cuda/H100/newton/newton_robot_cartpole @@ -0,0 +1,8 @@ +#!/bin/bash +# Wrapper for Newton robot cartpole benchmark + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setup_newton.sh" + +# Run Newton example +python -m newton.examples.robot.example_robot_cartpole --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/setup_newton.sh b/src/cuda/H100/newton/setup_newton.sh new file mode 100755 index 000000000..28b213c81 --- /dev/null +++ b/src/cuda/H100/newton/setup_newton.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Setup Newton environment (similar to huggingface/setup_environment.sh) + +NEWTON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NEWTON_SUBMODULE="$NEWTON_DIR/../external/newton" + +# Create Python virtual environment if it doesn't exist +if [ ! -d "$NEWTON_DIR/newton_venv" ]; then + echo "Creating Python virtual environment for Newton..." + python3 -m venv "$NEWTON_DIR/newton_venv" + source "$NEWTON_DIR/newton_venv/bin/activate" + + # Install Newton and dependencies + pip install --upgrade pip + if [ -d "$NEWTON_SUBMODULE" ]; then + echo "Installing Newton from submodule..." + pip install -e "$NEWTON_SUBMODULE" + # Install additional dependencies for robot and USD examples + pip install usd-core mujoco-warp + else + echo "WARNING: Newton submodule not found at $NEWTON_SUBMODULE" + echo "Run: git submodule update --init --recursive" + fi +else + source "$NEWTON_DIR/newton_venv/bin/activate" +fi + +export NEWTON_ENV="$NEWTON_DIR/newton_venv" +export PYTHONPATH="$NEWTON_SUBMODULE:$PYTHONPATH" diff --git a/src/cuda/H100/setup_vpi.sh b/src/cuda/H100/setup_vpi.sh new file mode 100755 index 000000000..58f0c4994 --- /dev/null +++ b/src/cuda/H100/setup_vpi.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# VPI Setup Script +# Installs VPI library if not already present +# Requires sudo for system-wide installation + +set -e + +echo "VPI Setup Script" +echo "================" + +# Check if VPI is already installed +if command -v vpi-config &> /dev/null; then + VPI_VERSION=$(vpi-config --version) + VPI_PATH=$(vpi-config --prefix) + echo "VPI already installed: version $VPI_VERSION at $VPI_PATH" + exit 0 +fi + +# Check if running on x86_64 Linux +if [[ "$(uname -m)" != "x86_64" ]] || [[ "$(uname -s)" != "Linux" ]]; then + echo "ERROR: VPI installation is only supported on Linux x86_64" + exit 1 +fi + +# Detect Ubuntu version +if [[ -f /etc/os-release ]]; then + . /etc/os-release + UBUNTU_VERSION=$VERSION_ID +else + echo "ERROR: Cannot detect Ubuntu version" + exit 1 +fi + +echo "Detected Ubuntu $UBUNTU_VERSION" + +# Check for sudo +if ! command -v sudo &> /dev/null; then + echo "ERROR: sudo is required for VPI installation" + exit 1 +fi + +echo "" +echo "Installing VPI via apt..." +echo "This requires sudo privileges and will install system packages." +echo "" + +# Install prerequisites +sudo apt-get update +sudo apt-get install -y gnupg software-properties-common + +# Add NVIDIA repository key +sudo apt-key adv --fetch-key https://repo.download.nvidia.com/jetson/jetson-ota-public.asc + +# Add repository based on Ubuntu version +if [[ "$UBUNTU_VERSION" == "22.04" ]]; then + sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main' +elif [[ "$UBUNTU_VERSION" == "24.04" ]]; then + sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/noble r38.4 main' +else + echo "WARNING: Ubuntu $UBUNTU_VERSION not officially supported. Trying jammy repository..." + sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main' +fi + +# Install VPI packages +sudo apt-get update +sudo apt-get install -y libnvvpi4 vpi4-dev vpi4-samples + +# Detect Python version and install Python bindings +PYTHON_VERSION=$(python3 --version | awk '{print $2}' | cut -d. -f1,2) +if [[ "$PYTHON_VERSION" == "3.10" ]]; then + sudo apt-get install -y python3.10-vpi4 +elif [[ "$PYTHON_VERSION" == "3.12" ]]; then + sudo apt-get install -y python3.12-vpi4 +else + echo "WARNING: Python VPI bindings not available for Python $PYTHON_VERSION" + echo "Supported versions: 3.10, 3.12" +fi + +echo "" +echo "VPI installation complete!" +echo "VPI installed at: $(vpi-config --prefix)" +echo "VPI version: $(vpi-config --version)" diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt b/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt new file mode 100644 index 000000000..5ba6d5c46 --- /dev/null +++ b/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.18) +project(vpi_background_subtractor CXX) + +# Find VPI library (optional) +find_package(vpi QUIET) + +if(vpi_FOUND) + add_executable(vpi_background_subtractor main.cpp) + target_link_libraries(vpi_background_subtractor PRIVATE vpi) +else() + message(WARNING "VPI not found - skipping vpi_background_subtractor") +endif() diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp new file mode 100644 index 000000000..2f36df519 --- /dev/null +++ b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of NVIDIA CORPORATION nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CHECK_STATUS(STMT) \ + do \ + { \ + VPIStatus status = (STMT); \ + if (status != VPI_SUCCESS) \ + { \ + char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ + vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ + std::ostringstream ss; \ + ss << vpiStatusGetName(status) << ": " << buffer; \ + throw std::runtime_error(ss.str()); \ + } \ + } while (0); + +int main(int argc, char *argv[]) +{ + // OpenCV image that will be wrapped by a VPIImage. + // Define it here so that it's destroyed *after* wrapper is destroyed + cv::Mat cvCurFrame; + + // VPI objects that will be used + VPIStream stream = NULL; + VPIImage imgCurFrame = NULL; + VPIImage bgimage = NULL; + VPIImage fgmask = NULL; + VPIPayload payload = NULL; + + int retval = 0; + + try + { + if (argc != 3 && argc != 4) + { + throw std::runtime_error(std::string("Usage: ") + argv[0] + " [max_frames]"); + } + + // Parse input parameters + std::string strBackend = argv[1]; + std::string strInputVideo = argv[2]; + int maxFrames = -1; // -1 means process all frames + if (argc == 4) + { + maxFrames = std::atoi(argv[3]); + } + + VPIBackend backend; + if (strBackend == "cpu") + { + backend = VPI_BACKEND_CPU; + } + else if (strBackend == "cuda") + { + backend = VPI_BACKEND_CUDA; + } + else + { + throw std::runtime_error("Backend '" + strBackend + "' not recognized."); + } + + // Load the input video + cv::VideoCapture invid; + if (!invid.open(strInputVideo)) + { + throw std::runtime_error("Can't open '" + strInputVideo + "'"); + } + + int32_t width = invid.get(cv::CAP_PROP_FRAME_WIDTH); + int32_t height = invid.get(cv::CAP_PROP_FRAME_HEIGHT); + + // Create the stream where processing will happen. We'll use user-provided backend. + CHECK_STATUS(vpiStreamCreate(backend, &stream)); + + // Create background subtractor payload to be executed on the given backend + // OpenCV delivers us BGR8 images, so the algorithm is configured to accept that. + CHECK_STATUS(vpiCreateBackgroundSubtractor(backend, width, height, VPI_IMAGE_FORMAT_BGR8, &payload)); + + // Create foreground image + CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_U8, 0, &fgmask)); + + // Create background image + CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_BGR8, 0, &bgimage)); + + int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G'); + double fps = invid.get(cv::CAP_PROP_FPS); + + cv::VideoWriter outVideo("fgmask_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height), false); + if (!outVideo.isOpened()) + { + throw std::runtime_error("Can't create output video"); + } + + cv::VideoWriter bgimageVideo("bgimage_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height)); + if (!outVideo.isOpened()) + { + throw std::runtime_error("Can't create output video"); + } + + // Fetch a new frame until video ends + int idxFrame = 1; + + while (invid.read(cvCurFrame)) + { + printf("Processing frame %d\n", idxFrame); + + // Check if we've reached max frames limit + if (maxFrames > 0 && idxFrame > maxFrames) + { + printf("Reached max frames limit (%d), stopping.\n", maxFrames); + break; + } + idxFrame++; + // Wrap frame into a VPIImage + if (imgCurFrame == NULL) + { + CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvCurFrame, 0, &imgCurFrame)); + } + else + { + CHECK_STATUS(vpiImageSetWrappedOpenCVMat(imgCurFrame, cvCurFrame)); + } + + VPIBackgroundSubtractorParams params; + CHECK_STATUS(vpiInitBackgroundSubtractorParams(¶ms)); + params.learningRate = 0.01; + + CHECK_STATUS( + vpiSubmitBackgroundSubtractor(stream, backend, payload, imgCurFrame, fgmask, bgimage, ¶ms)); + + // Wait for processing to finish. + CHECK_STATUS(vpiStreamSync(stream)); + + { + // Now add it to the output video stream + VPIImageData imgdata; + CHECK_STATUS(vpiImageLockData(fgmask, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgdata)); + + cv::Mat outFrame; + CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame)); + + outVideo << outFrame; + + CHECK_STATUS(vpiImageUnlock(fgmask)); + } + + { + VPIImageData bgdata; + CHECK_STATUS(vpiImageLockData(bgimage, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &bgdata)); + + cv::Mat outFrame; + CHECK_STATUS(vpiImageDataExportOpenCVMat(bgdata, &outFrame)); + + bgimageVideo << outFrame; + + CHECK_STATUS(vpiImageUnlock(bgimage)); + } + } + } + catch (std::exception &e) + { + std::cerr << e.what() << std::endl; + retval = 1; + } + + // Destroy all resources used + vpiStreamDestroy(stream); + vpiPayloadDestroy(payload); + + vpiImageDestroy(imgCurFrame); + vpiImageDestroy(fgmask); + vpiImageDestroy(bgimage); + + return retval; +} diff --git a/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt b/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt new file mode 100644 index 000000000..548e8fba6 --- /dev/null +++ b/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.18) +project(vpi_orb_feature_detector CXX) + +find_package(vpi QUIET) + +if(vpi_FOUND) + add_executable(vpi_orb_feature_detector main.cpp) + target_link_libraries(vpi_orb_feature_detector PRIVATE vpi) +else() + message(WARNING "VPI not found - skipping vpi_orb_feature_detector") +endif() diff --git a/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp b/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp new file mode 100644 index 000000000..61c57a107 --- /dev/null +++ b/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp @@ -0,0 +1,289 @@ +/* +* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of NVIDIA CORPORATION nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHECK_STATUS(STMT) \ + do \ + { \ + VPIStatus status = (STMT); \ + if (status != VPI_SUCCESS) \ + { \ + char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ + vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ + std::ostringstream ss; \ + ss << vpiStatusGetName(status) << ": " << buffer; \ + throw std::runtime_error(ss.str()); \ + } \ + } while (0); + +static cv::Mat DrawKeypoints(cv::Mat img, VPIPyramidalKeypointF32 *kpts, VPIBriefDescriptor *descs, int numKeypoints) +{ + cv::Mat out; + img.convertTo(out, CV_8UC1); + cvtColor(out, out, cv::COLOR_GRAY2BGR); + + if (numKeypoints == 0) + { + return out; + } + + std::vector distances(numKeypoints, 0); + float maxDist = 0.f; + + for (int i = 0; i < numKeypoints; i++) + { + for (int j = 0; j < VPI_BRIEF_DESCRIPTOR_ARRAY_LENGTH; j++) + { + distances[i] += std::bitset<8 * sizeof(uint8_t)>(descs[i].data[j] ^ descs[0].data[j]).count(); + } + if (distances[i] > maxDist) + { + maxDist = distances[i]; + } + } + + uint8_t ids[256]; + std::iota(&ids[0], &ids[0] + 256, 0); + cv::Mat idsMat(256, 1, CV_8UC1, ids); + + cv::Mat cmap; + applyColorMap(idsMat, cmap, cv::COLORMAP_JET); + + for (int i = 0; i < numKeypoints; i++) + { + int cmapIdx = static_cast(std::round((distances[i] / maxDist) * 255)); + + float rescale = std::pow(2, kpts[i].octave); + float x = kpts[i].x * rescale; + float y = kpts[i].y * rescale; + + circle(out, cv::Point(x, y), 3, cmap.at(cmapIdx, 0), -1); + } + + return out; +} + +int main(int argc, char *argv[]) +{ + // OpenCV image that will be wrapped by a VPIImage. + // Define it here so that it's destroyed *after* wrapper is destroyed + cv::Mat cvImage; + + // VPI objects that will be used + VPIImage imgInput = NULL; + VPIImage imgGrayScale = NULL; + + VPIPyramid pyrInput = NULL; + VPIArray keypoints = NULL; + VPIArray descriptors = NULL; + VPIPayload orbPayload = NULL; + VPIStream stream = NULL; + + int retval = 0; + + try + { + // ============================= + // Parse command line parameters + + if (argc != 3) + { + throw std::runtime_error(std::string("Usage: ") + argv[0] + " "); + } + + std::string strBackend = argv[1]; + std::string strInputFileName = argv[2]; + + // Now parse the backend + VPIBackend backend; + + if (strBackend == "cpu") + { + backend = VPI_BACKEND_CPU; + } + else if (strBackend == "cuda") + { + backend = VPI_BACKEND_CUDA; + } + else if (strBackend == "pva") + { + backend = VPI_BACKEND_PVA; + } + else + { + throw std::runtime_error("Backend '" + strBackend + + "' not recognized, it must be either cpu, cuda or pva."); + } + + // Use the selected backend with CPU to be able to read data back from CUDA to CPU for example. + const VPIBackend backendWithCPU = static_cast(backend | VPI_BACKEND_CPU); + + // ===================== + // Load the input image + + cvImage = cv::imread(strInputFileName); + if (cvImage.empty()) + { + throw std::runtime_error("Can't open first image: '" + strInputFileName + "'"); + } + + // ================================= + // Allocate all VPI resources needed + + // Create the stream where processing will happen + CHECK_STATUS(vpiStreamCreate(0, &stream)); + + // Define the algorithm parameters. + VPIORBParams orbParams; + CHECK_STATUS(vpiInitORBParams(&orbParams)); + + orbParams.fastParams.intensityThreshold = 142; + orbParams.maxFeaturesPerLevel = 88; + orbParams.maxPyramidLevels = 3; + + // PVA backend doesn't support default Harris Corner Scoring + if (backend == VPI_BACKEND_PVA) + { + orbParams.scoreType = VPI_CORNER_SCORE_FAST; + } + + // We now wrap the loaded image into a VPIImage object to be used by VPI. + // VPI won't make a copy of it, so the original image must be in scope at all times. + CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImage, 0, &imgInput)); + CHECK_STATUS(vpiImageCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, 0, &imgGrayScale)); + + // For the output arrays capacity we can use the maximum number of features per level multiplied by the + // maximum number of pyramid levels, this will be the de factor maximum for all levels of the input. + int outCapacity = orbParams.maxFeaturesPerLevel * orbParams.maxPyramidLevels; + + // Create the output keypoint array. + CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_PYRAMIDAL_KEYPOINT_F32, backendWithCPU, &keypoints)); + + // Create the output descriptors array. To output corners only use NULL instead. + CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_BRIEF_DESCRIPTOR, backendWithCPU, &descriptors)); + + // For the internal buffers capacity we can use the maximum number of features per level multiplied by 20. + // This will make FAST find a large number of corners so then ORB can select the top N corners in + // accordance to Harris score of each corner, where N = maximum number of features per level. + int bufCapacity = orbParams.maxFeaturesPerLevel * 20; + + // Create the payload for ORB Feature Detector algorithm + CHECK_STATUS(vpiCreateORBFeatureDetector(backend, bufCapacity, &orbPayload)); + + // ================ + // Processing stage + + // First convert input to grayscale + VPIBackend convertBackend = (backend == VPI_BACKEND_PVA) ? VPI_BACKEND_CPU : backend; + CHECK_STATUS(vpiSubmitConvertImageFormat(stream, convertBackend, imgInput, imgGrayScale, NULL)); + + // Then, create the Gaussian Pyramid for the image and wait for the execution to finish + CHECK_STATUS(vpiPyramidCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, orbParams.maxPyramidLevels, 0.5, + backend, &pyrInput)); + CHECK_STATUS(vpiSubmitGaussianPyramidGenerator(stream, backend, imgGrayScale, pyrInput, VPI_BORDER_CLAMP)); + + // Then get ORB features and wait for the execution to finish + CHECK_STATUS(vpiSubmitORBFeatureDetector(stream, backend, orbPayload, pyrInput, keypoints, descriptors, + &orbParams, VPI_BORDER_CLAMP)); + + CHECK_STATUS(vpiStreamSync(stream)); + + // ======================================= + // Output processing and saving it to disk + + // Lock output keypoints and scores to retrieve its data on cpu memory + VPIArrayData outKeypointsData; + VPIArrayData outDescriptorsData; + VPIImageData imgData; + CHECK_STATUS(vpiArrayLockData(keypoints, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outKeypointsData)); + CHECK_STATUS(vpiArrayLockData(descriptors, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outDescriptorsData)); + CHECK_STATUS(vpiImageLockData(imgGrayScale, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgData)); + + VPIPyramidalKeypointF32 *outKeypoints = (VPIPyramidalKeypointF32 *)outKeypointsData.buffer.aos.data; + VPIBriefDescriptor *outDescriptors = (VPIBriefDescriptor *)outDescriptorsData.buffer.aos.data; + + cv::Mat img; + CHECK_STATUS(vpiImageDataExportOpenCVMat(imgData, &img)); + + // Draw the keypoints in the output image + cv::Mat outImage = DrawKeypoints(img, outKeypoints, outDescriptors, *outKeypointsData.buffer.aos.sizePointer); + + // Save the output image to disk + imwrite("orb_feature_detector_" + strBackend + ".png", outImage); + + // Done handling outputs, don't forget to unlock them. + CHECK_STATUS(vpiImageUnlock(imgGrayScale)); + CHECK_STATUS(vpiArrayUnlock(keypoints)); + CHECK_STATUS(vpiArrayUnlock(descriptors)); + } + catch (std::exception &e) + { + std::cerr << e.what() << std::endl; + retval = 1; + } + + // ======== + // Clean up + + // Make sure stream is synchronized before destroying the objects + // that might still be in use. + vpiStreamSync(stream); + + vpiImageDestroy(imgInput); + vpiImageDestroy(imgGrayScale); + vpiArrayDestroy(keypoints); + vpiArrayDestroy(descriptors); + vpiPayloadDestroy(orbPayload); + vpiStreamDestroy(stream); + + return retval; +} diff --git a/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt b/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt new file mode 100644 index 000000000..927fd8d08 --- /dev/null +++ b/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.18) +project(vpi_stereo_disparity CXX) + +find_package(vpi QUIET) + +if(vpi_FOUND) + add_executable(vpi_stereo_disparity main.cpp) + target_link_libraries(vpi_stereo_disparity PRIVATE vpi) +else() + message(WARNING "VPI not found - skipping vpi_stereo_disparity") +endif() diff --git a/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp b/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp new file mode 100644 index 000000000..c14e35a5c --- /dev/null +++ b/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp @@ -0,0 +1,354 @@ +/* +* Copyright (c) 2019-2024, NVIDIA CORPORATION. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of NVIDIA CORPORATION nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#if CV_MAJOR_VERSION >= 3 +# include +#else +# include // for colormap +# include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CHECK_STATUS(STMT) \ + do \ + { \ + VPIStatus status = (STMT); \ + if (status != VPI_SUCCESS) \ + { \ + char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ + vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ + std::ostringstream ss; \ + ss << "line " << __LINE__ << " " << vpiStatusGetName(status) << ": " << buffer; \ + throw std::runtime_error(ss.str()); \ + } \ + } while (0); + +int main(int argc, char *argv[]) +{ + // OpenCV image that will be wrapped by a VPIImage. + // Define it here so that it's destroyed *after* wrapper is destroyed + cv::Mat cvImageLeft, cvImageRight; + + // VPI objects that will be used + VPIImage inLeft = NULL; + VPIImage inRight = NULL; + VPIImage tmpLeft = NULL; + VPIImage tmpRight = NULL; + VPIImage stereoLeft = NULL; + VPIImage stereoRight = NULL; + VPIImage disparity = NULL; + VPIImage confidenceMap = NULL; + VPIStream stream = NULL; + VPIPayload stereo = NULL; + + int retval = 0; + + try + { + // ============================= + // Parse command line parameters + + if (argc != 4) + { + throw std::runtime_error(std::string("Usage: ") + argv[0] + + " "); + } + + std::string strBackend = argv[1]; + std::string strLeftFileName = argv[2]; + std::string strRightFileName = argv[3]; + + uint64_t backends; + + if (strBackend == "cuda") + { + backends = VPI_BACKEND_CUDA; + } + else if (strBackend == "ofa") + { + backends = VPI_BACKEND_OFA; + } + else if (strBackend == "ofa-pva-vic") + { + backends = VPI_BACKEND_OFA | VPI_BACKEND_PVA | VPI_BACKEND_VIC; + } + else + { + throw std::runtime_error("Backend '" + strBackend + + "' not recognized, it must be either cuda, ofa or ofa-pva-vic."); + } + + // ===================== + // Load the input images + cvImageLeft = cv::imread(strLeftFileName); + if (cvImageLeft.empty()) + { + throw std::runtime_error("Can't open '" + strLeftFileName + "'"); + } + + cvImageRight = cv::imread(strRightFileName); + if (cvImageRight.empty()) + { + throw std::runtime_error("Can't open '" + strRightFileName + "'"); + } + + // ================================= + // Allocate all VPI resources needed + + int32_t inputWidth = cvImageLeft.cols; + int32_t inputHeight = cvImageLeft.rows; + + // Create the stream that will be used for processing. + CHECK_STATUS(vpiStreamCreate(0, &stream)); + + // We now wrap the loaded images into a VPIImage object to be used by VPI. + // VPI won't make a copy of it, so the original image must be in scope at all times. + CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImageLeft, 0, &inLeft)); + CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImageRight, 0, &inRight)); + + // Format conversion parameters needed for input pre-processing + VPIConvertImageFormatParams convParams; + CHECK_STATUS(vpiInitConvertImageFormatParams(&convParams)); + + // Initialize default parameters + VPIStereoDisparityEstimatorCreationParams createParams; + CHECK_STATUS(vpiInitStereoDisparityEstimatorCreationParams(&createParams)); + + // Select max disparity that works well for the chair_stereo_{left,right}_1920.png files + createParams.maxDisparity = 256; + + // Default format and size for input stereo pair (some backends require adjustments, see below) + VPIImageFormat stereoFormat = VPI_IMAGE_FORMAT_Y8_ER; + + int stereoWidth = inputWidth; + int stereoHeight = inputHeight; + + // Default format and size for output + VPIImageFormat disparityFormat = VPI_IMAGE_FORMAT_S16; + + int outputWidth = inputWidth; + int outputHeight = inputHeight; + + // Override some backend-dependent parameters + if (strBackend.find("ofa") != std::string::npos) + { + // Implementations using OFA require BL input + stereoFormat = VPI_IMAGE_FORMAT_Y8_ER_BL; + + if (strBackend == "ofa") + { + // when using OFA alone, output must also be BL + disparityFormat = VPI_IMAGE_FORMAT_S16_BL; + } + + // Using downscale factor with OFA improves performance + createParams.downscaleFactor = 2; + outputWidth = (inputWidth + createParams.downscaleFactor - 1) / createParams.downscaleFactor; + outputHeight = (inputHeight + createParams.downscaleFactor - 1) / createParams.downscaleFactor; + + // Output width including downscaleFactor must be at least max(64, maxDisparity/downscaleFactor) when the + // OFA+PVA+VIC backend is used + if (strBackend.find("pva") != std::string::npos) + { + int minWidth = std::max(createParams.maxDisparity / createParams.downscaleFactor, outputWidth); + outputWidth = std::max(64, minWidth); + outputHeight = (inputHeight * outputWidth) / inputWidth; + stereoWidth = outputWidth * createParams.downscaleFactor; + stereoHeight = outputHeight * createParams.downscaleFactor; + } + } + + // Create the payload for Stereo Disparity algorithm. + // Payload is created before the image objects so that non-supported backends can be trapped with an error. + CHECK_STATUS(vpiCreateStereoDisparityEstimator(backends, stereoWidth, stereoHeight, stereoFormat, &createParams, + &stereo)); + + // Create the output image where the disparity map will be stored. + CHECK_STATUS(vpiImageCreate(outputWidth, outputHeight, disparityFormat, 0, &disparity)); + + // Create the input stereo images + CHECK_STATUS(vpiImageCreate(stereoWidth, stereoHeight, stereoFormat, 0, &stereoLeft)); + CHECK_STATUS(vpiImageCreate(stereoWidth, stereoHeight, stereoFormat, 0, &stereoRight)); + + // Create the confidence image if the backend can support it + if (strBackend == "ofa-pva-vic" || strBackend == "cuda") + { + CHECK_STATUS(vpiImageCreate(outputWidth, outputHeight, VPI_IMAGE_FORMAT_U16, 0, &confidenceMap)); + } + + // If a rescale of the input is required, create temporary images for the initial format conversion. + bool const isRescaleRequired = (stereoWidth != inputWidth) || (stereoHeight != inputHeight); + if (isRescaleRequired) + { + CHECK_STATUS(vpiImageCreate(inputWidth, inputHeight, stereoFormat, 0, &tmpLeft)); + CHECK_STATUS(vpiImageCreate(inputWidth, inputHeight, stereoFormat, 0, &tmpRight)); + } + + // ================ + // Processing stage + + // Start with default parameters, and override some values depending on what backend is used. + VPIStereoDisparityEstimatorParams submitParams; + CHECK_STATUS(vpiInitStereoDisparityEstimatorParams(&submitParams)); + if (strBackend == "ofa-pva-vic") + { + // The INFERENCE confidence type achieves better performance with OFA+PVA+VIC backend. The only tradeoff is + // that the deep-learning based confidence map is not easily expressed as a function of left and right + // disparity estimates, in contrast to ABSOLUTE or RELATIVE confidence type. + submitParams.confidenceType = VPI_STEREO_CONFIDENCE_INFERENCE; + } + else if (strBackend == "cuda") + { + // The chair_stereo_{left,right}_1920.png inputs benefit from a higher confidence threshold with CUDA + submitParams.confidenceThreshold = UINT16_MAX - 10000; + } + + // ----------------- + // Pre-process input + if (isRescaleRequired) + { + // We require a conversion with CUDA only because we loaded the images in the default BGR format from OpenCV + // and the VIC backend does not support 3-channel RGB/BGR image formats. + // Alternatively, we could load grayscale images and handle the conversion+rescale in one operation on VIC. + + // Convert opencv input to grayscale format using CUDA + CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inLeft, tmpLeft, &convParams)); + CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inRight, tmpRight, &convParams)); + + // Rescale on VIC + CHECK_STATUS( + vpiSubmitRescale(stream, VPI_BACKEND_VIC, tmpLeft, stereoLeft, VPI_INTERP_LINEAR, VPI_BORDER_CLAMP, 0)); + CHECK_STATUS(vpiSubmitRescale(stream, VPI_BACKEND_VIC, tmpRight, stereoRight, VPI_INTERP_LINEAR, + VPI_BORDER_CLAMP, 0)); + } + else + { + // Convert opencv input to grayscale format using CUDA + CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inLeft, stereoLeft, &convParams)); + CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inRight, stereoRight, &convParams)); + } + + // ------------------------------ + // Do stereo disparity estimation + + // Submit it with the input and output images + CHECK_STATUS(vpiSubmitStereoDisparityEstimator(stream, backends, stereo, stereoLeft, stereoRight, disparity, + confidenceMap, &submitParams)); + + // Wait until the algorithm finishes processing + CHECK_STATUS(vpiStreamSync(stream)); + + // ======================================== + // Output pre-processing and saving to disk + // Lock output to retrieve its data on cpu memory + VPIImageData data; + CHECK_STATUS(vpiImageLockData(disparity, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &data)); + + // Make an OpenCV matrix out of this image + cv::Mat cvDisparity; + CHECK_STATUS(vpiImageDataExportOpenCVMat(data, &cvDisparity)); + + // Scale result and write it to disk. Disparities are in Q10.5 format, + // so to map it to float, it gets divided by 32. Then the resulting disparity range, + // from 0 to maxDisparity gets mapped to 0-255 for proper output. + cvDisparity.convertTo(cvDisparity, CV_8UC1, 255.0 / (32 * createParams.maxDisparity), 0); + + // Apply JET colormap to turn the disparities into color. + // Reddish hues represent objects closer to the camera, blueish are farther away. + cv::Mat cvDisparityColor; + applyColorMap(cvDisparity, cvDisparityColor, cv::COLORMAP_JET); + + // Done handling output, don't forget to unlock it. + CHECK_STATUS(vpiImageUnlock(disparity)); + + // If we have a confidence map, adjust it for display and write it to disk too. + if (confidenceMap) + { + // Lock the image data and export to cv::Mat + VPIImageData data; + CHECK_STATUS(vpiImageLockData(confidenceMap, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &data)); + cv::Mat cvConfidence; + CHECK_STATUS(vpiImageDataExportOpenCVMat(data, &cvConfidence)); + + // Confidence map varies from 0 to 65535, we scale it to [0-255]. + cvConfidence.convertTo(cvConfidence, CV_8UC1, 255.0 / 65535, 0); + imwrite("confidence_" + strBackend + ".png", cvConfidence); + + CHECK_STATUS(vpiImageUnlock(confidenceMap)); + + // When pixel confidence is 0, we would like its color in the disparity image to be black. + cv::Mat cvMask; + threshold(cvConfidence, cvMask, 1, 255, cv::THRESH_BINARY); + cvtColor(cvMask, cvMask, cv::COLOR_GRAY2BGR); + bitwise_and(cvDisparityColor, cvMask, cvDisparityColor); + } + + imwrite("disparity_" + strBackend + ".png", cvDisparityColor); + } + catch (std::exception &e) + { + std::cerr << e.what() << std::endl; + retval = 1; + } + + // ======== + // Clean up + + // Destroying stream first makes sure that all work submitted to + // it is finished. + vpiStreamDestroy(stream); + + // Only then we can destroy the other objects, as we're sure they + // aren't being used anymore. + + vpiImageDestroy(inLeft); + vpiImageDestroy(inRight); + vpiImageDestroy(tmpLeft); + vpiImageDestroy(tmpRight); + vpiImageDestroy(stereoLeft); + vpiImageDestroy(stereoRight); + vpiImageDestroy(confidenceMap); + vpiImageDestroy(disparity); + vpiPayloadDestroy(stereo); + + return retval; +} diff --git a/src/cuda/cutlass-bench b/src/cuda/cutlass-bench index e51efbfe1..291300fff 160000 --- a/src/cuda/cutlass-bench +++ b/src/cuda/cutlass-bench @@ -1 +1 @@ -Subproject commit e51efbfe18fe4f4cbb66ab814c55bf4aa0185491 +Subproject commit 291300ffffa3533a78ee104f08a8490a29ce9ccb diff --git a/src/cuda/pytorch_examples b/src/cuda/pytorch_examples index 30d1a73e5..1bef748fa 160000 --- a/src/cuda/pytorch_examples +++ b/src/cuda/pytorch_examples @@ -1 +1 @@ -Subproject commit 30d1a73e5ab3bb92e5834dc11805e53ee8aee2c7 +Subproject commit 1bef748fab064e2fc3beddcbda60fd51cb9612d2 diff --git a/src/setup_environment b/src/setup_environment index 21b42af72..29a1dbba4 100755 --- a/src/setup_environment +++ b/src/setup_environment @@ -208,4 +208,11 @@ else fi fi +# Check for VPI (optional for H100 VPI apps) +if command -v vpi-config &> /dev/null; then + export VPI_INSTALL_PATH=$(vpi-config --prefix) + export VPI_VERSION=$(vpi-config --version) + echo "VPI found: version $VPI_VERSION at $VPI_INSTALL_PATH" +fi + export GPUAPPS_SETUP_ENVIRONMENT_WAS_RUN=1 From fb56a324417759fe901c83d6864c2968aec413c3 Mon Sep 17 00:00:00 2001 From: Anusuya Nallathambi Date: Sun, 22 Mar 2026 16:12:05 -0400 Subject: [PATCH 02/11] Update src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile index 9f1c4d3de..d846333d2 100644 --- a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile @@ -1,6 +1,6 @@ EXECUTABLE := cufft_lto_r2c_c2r_scalable -CUFILES := cufft_lto_r2c_c2r_scalable.cu -CCFILES := +CUFILES := +CCFILES := cufft_lto_r2c_c2r_scalable.cpp ADDITIONAL_LIBS := -lcufft OMIT_CUTIL_LIB := 1 OMIT_SHRUTIL_LIB := 1 From 3281c8b3f60c4cfd64b7dfa6548a02e2d9099f65 Mon Sep 17 00:00:00 2001 From: Anunalla Date: Sun, 22 Mar 2026 20:26:44 +0000 Subject: [PATCH 03/11] H100 new apps-copilot suggestions fix --- README.md | 2 +- src/Makefile | 1 + src/cuda/H100/README.md | 4 ++-- .../cusolver_ormqr/cusolver_ormqr_scalable.cu | 10 +++++----- src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu | 3 +++ src/cuda/H100/graph/mst_standalone/mst_standalone.cu | 3 +++ src/cuda/H100/setup_vpi.sh | 11 ++++++----- src/cuda/H100/vpi/vpi_background_subtractor/main.cpp | 4 ++-- 8 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f09602671..02a0fd9de 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,6 @@ The H100 suite contains 15 modern GPU workloads from H100 profiling and analysis - **Image Processing** (3 apps): Wavelet transform, Gaussian filter, FDTD3d - **Graph Algorithms** (2 apps): BFS and MST using cuGraph (git submodule) - **Physics Simulation** (3 apps): Newton physics engine benchmarks (git submodule) -- **Computer Vision** (3 apps): VPI-based vision processing (optional, requires VPI 4.0) +- **Computer Vision** (3 apps): VPI-based vision processing (requires VPI 4.0) See [src/cuda/H100/README.md](src/cuda/H100/README.md) for details. diff --git a/src/Makefile b/src/Makefile index 0c1db7882..4bd8a5d2c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -557,6 +557,7 @@ H100: mv cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_ormqr mv cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_Xgetrf mv cuda/H100/image/dwtHaar1D/dwtHaar1D $(BINDIR)/$(BINSUBDIR)/H100-dwtHaar1D + mv cuda/H100/image/recursiveGaussian/recursiveGaussian $(BINDIR)/$(BINSUBDIR)/H100-recursiveGaussian mv cuda/H100/image/FDTD3d/FDTD3d $(BINDIR)/$(BINSUBDIR)/H100-FDTD3d # Build graph apps (cuGraph submodule) -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 graph && \ diff --git a/src/cuda/H100/README.md b/src/cuda/H100/README.md index 3c19ec6e6..3eedbeaab 100644 --- a/src/cuda/H100/README.md +++ b/src/cuda/H100/README.md @@ -75,8 +75,8 @@ bin/*/release/H100-recursiveGaussian bin/*/release/H100-FDTD3d # Graph apps (with generated data) -bin/*/release/H100-bfs data_dirs/cuda/H100/graph/karate.mtx -bin/*/release/H100-mst data_dirs/cuda/H100/graph/netscience.mtx +bin/*/release/H100-bfs --file data_dirs/cuda/H100/graph/karate.mtx +bin/*/release/H100-mst --file data_dirs/cuda/H100/graph/netscience.mtx # Newton apps bin/*/release/newton/newton_diffsim_ball diff --git a/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu index ee0141fc0..4eb2e54ab 100644 --- a/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu +++ b/src/cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu @@ -4,8 +4,8 @@ * * Accepts command-line arguments for matrix size: * small: 16x16 - * medium: 128x128 - * large: 512x512 + * medium: 256x256 + * large: 768x768 */ #include @@ -24,13 +24,13 @@ void print_usage(const char* prog_name) { printf("Usage: %s [size]\n", prog_name); printf("\n"); printf("Arguments:\n"); - printf(" size Matrix size: small (16x16), medium (128x128), large (512x512)\n"); + printf(" size Matrix size: small (16x16), medium (256x256), large (768x768)\n"); printf(" Default: small\n"); printf("\n"); printf("Examples:\n"); printf(" %s small # 16x16 matrix\n", prog_name); - printf(" %s medium # 128x128 matrix\n", prog_name); - printf(" %s large # 512x512 matrix\n", prog_name); + printf(" %s medium # 256x256 matrix\n", prog_name); + printf(" %s large # 768x768 matrix\n", prog_name); printf("\n"); } diff --git a/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu b/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu index 1217ffe0f..1a651b3e0 100644 --- a/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu +++ b/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include // Simple graph loader for MTX format template diff --git a/src/cuda/H100/graph/mst_standalone/mst_standalone.cu b/src/cuda/H100/graph/mst_standalone/mst_standalone.cu index c9d52f55d..97059096b 100644 --- a/src/cuda/H100/graph/mst_standalone/mst_standalone.cu +++ b/src/cuda/H100/graph/mst_standalone/mst_standalone.cu @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include // Simple graph loader for MTX format template diff --git a/src/cuda/H100/setup_vpi.sh b/src/cuda/H100/setup_vpi.sh index 58f0c4994..1f38c4f52 100755 --- a/src/cuda/H100/setup_vpi.sh +++ b/src/cuda/H100/setup_vpi.sh @@ -48,17 +48,18 @@ echo "" sudo apt-get update sudo apt-get install -y gnupg software-properties-common -# Add NVIDIA repository key -sudo apt-key adv --fetch-key https://repo.download.nvidia.com/jetson/jetson-ota-public.asc +# Add NVIDIA repository key (modern method) +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://repo.download.nvidia.com/jetson/jetson-ota-public.asc | sudo gpg --dearmor -o /etc/apt/keyrings/nvidia-jetson.gpg # Add repository based on Ubuntu version if [[ "$UBUNTU_VERSION" == "22.04" ]]; then - sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main' + echo "deb [signed-by=/etc/apt/keyrings/nvidia-jetson.gpg] https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main" | sudo tee /etc/apt/sources.list.d/nvidia-jetson.list elif [[ "$UBUNTU_VERSION" == "24.04" ]]; then - sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/noble r38.4 main' + echo "deb [signed-by=/etc/apt/keyrings/nvidia-jetson.gpg] https://repo.download.nvidia.com/jetson/x86_64/noble r38.4 main" | sudo tee /etc/apt/sources.list.d/nvidia-jetson.list else echo "WARNING: Ubuntu $UBUNTU_VERSION not officially supported. Trying jammy repository..." - sudo add-apt-repository -y 'deb https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main' + echo "deb [signed-by=/etc/apt/keyrings/nvidia-jetson.gpg] https://repo.download.nvidia.com/jetson/x86_64/jammy r38.4 main" | sudo tee /etc/apt/sources.list.d/nvidia-jetson.list fi # Install VPI packages diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp index 2f36df519..f7de2fbaa 100644 --- a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp +++ b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp @@ -136,9 +136,9 @@ int main(int argc, char *argv[]) } cv::VideoWriter bgimageVideo("bgimage_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height)); - if (!outVideo.isOpened()) + if (!bgimageVideo.isOpened()) { - throw std::runtime_error("Can't create output video"); + throw std::runtime_error("Can't create background image video"); } // Fetch a new frame until video ends From f7de66e564a0de2e9fa1bab46dafd5aa4b050d0c Mon Sep 17 00:00:00 2001 From: Anunalla Date: Tue, 24 Mar 2026 03:50:50 +0000 Subject: [PATCH 04/11] Corrected build errors --- get_data.sh | 17 +- src/Makefile | 31 +- src/cuda/H100/Makefile | 50 +-- .../H100/cufft/cufft_lto_r2c_c2r/Makefile | 19 +- src/cuda/H100/generate_large_ppm.py | 96 ++++++ src/cuda/H100/generate_large_signal.py | 105 +++++++ src/cuda/H100/get_dwt_data.sh | 27 +- src/cuda/H100/get_graph_data.sh | 2 +- src/cuda/H100/get_image_data.sh | 9 +- src/cuda/H100/get_vpi_data.sh | 33 ++ src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu | 65 ++-- .../diffsim_ball/example_diffsim_ball.py | 284 ++++++++++++++++++ src/cuda/H100/newton/newton_diffsim_ball | 8 - src/cuda/H100/newton/newton_mpm_granular | 8 - src/cuda/H100/newton/newton_robot_cartpole | 8 - .../robot_cartpole/example_robot_cartpole.py | 206 +++++++++++++ .../vpi_background_subtractor/CMakeLists.txt | 50 ++- .../vpi/vpi_background_subtractor/main.cpp | 4 +- .../vpi_orb_feature_detector/CMakeLists.txt | 42 ++- .../vpi/vpi_stereo_disparity/CMakeLists.txt | 42 ++- src/cuda/common/common.mk | 7 +- 21 files changed, 975 insertions(+), 138 deletions(-) create mode 100755 src/cuda/H100/generate_large_ppm.py create mode 100755 src/cuda/H100/generate_large_signal.py create mode 100755 src/cuda/H100/get_vpi_data.sh create mode 100644 src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py delete mode 100755 src/cuda/H100/newton/newton_diffsim_ball delete mode 100755 src/cuda/H100/newton/newton_mpm_granular delete mode 100755 src/cuda/H100/newton/newton_robot_cartpole create mode 100644 src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py diff --git a/get_data.sh b/get_data.sh index 37d91b308..678a76a21 100755 --- a/get_data.sh +++ b/get_data.sh @@ -3,13 +3,13 @@ export BASH_ROOT="$( cd "$( dirname "$BASH_SOURCE" )" && pwd )" DATA_SUBDIR="/data_dirs/" DATA_ROOT=$BASH_ROOT$DATA_SUBDIR -if [ ! -d $DATA_ROOT ]; then - if [ ! -f $BASH_ROOT/all.gpgpu-sim-app-data.tgz ]; then - wget https://engineering.purdue.edu/tgrogers/gpgpu-sim/benchmark_data/all.gpgpu-sim-app-data.tgz - fi - tar xzvf all.gpgpu-sim-app-data.tgz -C $BASH_ROOT - rm all.gpgpu-sim-app-data.tgz -fi +# if [ ! -d $DATA_ROOT ]; then +# if [ ! -f $BASH_ROOT/all.gpgpu-sim-app-data.tgz ]; then +# wget https://engineering.purdue.edu/tgrogers/gpgpu-sim/benchmark_data/all.gpgpu-sim-app-data.tgz +# fi +# tar xzvf all.gpgpu-sim-app-data.tgz -C $BASH_ROOT +# rm all.gpgpu-sim-app-data.tgz +# fi # Generate H100 benchmark data echo "Generating H100 benchmark data..." @@ -22,3 +22,6 @@ fi if [ -f $BASH_ROOT/src/cuda/H100/get_dwt_data.sh ]; then bash $BASH_ROOT/src/cuda/H100/get_dwt_data.sh || echo "Warning: DWT data generation failed" fi +if [ -f $BASH_ROOT/src/cuda/H100/get_vpi_data.sh ]; then + bash $BASH_ROOT/src/cuda/H100/get_vpi_data.sh +fi diff --git a/src/Makefile b/src/Makefile index 4bd8a5d2c..714e89d5d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -549,29 +549,24 @@ clean_cutlass: H100: mkdir -p $(BINDIR)/$(BINSUBDIR)/ # Initialize and update submodules (like cutlass pattern) - git submodule init && git submodule update + git submodule sync && git submodule update --init --recursive # Build simple apps (cuFFT, cuSolver, image processing) $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 simple - mv cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable $(BINDIR)/$(BINSUBDIR)/H100-cufft_3d_c2c - mv cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable $(BINDIR)/$(BINSUBDIR)/H100-cufft_lto_r2c_c2r - mv cuda/H100/cusolver/cusolver_ormqr/cusolver_ormqr_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_ormqr - mv cuda/H100/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable $(BINDIR)/$(BINSUBDIR)/H100-cusolver_Xgetrf - mv cuda/H100/image/dwtHaar1D/dwtHaar1D $(BINDIR)/$(BINSUBDIR)/H100-dwtHaar1D - mv cuda/H100/image/recursiveGaussian/recursiveGaussian $(BINDIR)/$(BINSUBDIR)/H100-recursiveGaussian - mv cuda/H100/image/FDTD3d/FDTD3d $(BINDIR)/$(BINSUBDIR)/H100-FDTD3d # Build graph apps (cuGraph submodule) - -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 graph && \ - cp cuda/H100/graph/bfs_standalone/build/bfs_standalone $(BINDIR)/$(BINSUBDIR)/H100-bfs && \ - cp cuda/H100/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/H100-mst + # -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 graph && \ + # cp cuda/H100/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ # Build Newton apps (Newton submodule) cp -r cuda/H100/newton $(BINDIR)/$(BINSUBDIR)/ bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh - chmod u+x $(BINDIR)/$(BINSUBDIR)/newton/newton_* - # Build VPI apps (if VPI installed) - @if [ -n "$(VPI_INSTALL_PATH)" ]; then \ - $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 vpi && \ - find cuda/H100/vpi/*/build -name "vpi_*" -type f -executable -exec cp {} $(BINDIR)/$(BINSUBDIR)/ \;; \ - fi + echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + # Build VPI apps (required) + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 vpi + cp cuda/H100/vpi/vpi_background_subtractor/build/vpi_background_subtractor $(BINDIR)/$(BINSUBDIR)/ + cp cuda/H100/vpi/vpi_orb_feature_detector/build/vpi_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/ + cp cuda/H100/vpi/vpi_stereo_disparity/build/vpi_stereo_disparity $(BINDIR)/$(BINSUBDIR)/ clean_H100: $(SETENV) $(MAKE) -C cuda/H100 clean @@ -747,7 +742,7 @@ clean_pytorch_examples: rm -f $(BINDIR)/$(BINSUBDIR)/inference_vae clean_cuda_samples: - $(MAKE) clean -C ./cuda/cuda-samples/build + -$(MAKE) clean -C ./cuda/cuda-samples/build 2>/dev/null || true clean_huggingface: rm -rf $(BINDIR)/$(BINSUBDIR)/huggingface diff --git a/src/cuda/H100/Makefile b/src/cuda/H100/Makefile index 847c75ba9..066ae9941 100644 --- a/src/cuda/H100/Makefile +++ b/src/cuda/H100/Makefile @@ -1,5 +1,5 @@ # H100 Benchmark Suite Makefile (following cutlass-bench pattern) -.PHONY: all simple graph newton vpi clean +.PHONY: all simple graph newton vpi clean cufft cusolver image # Default: Build all apps (submodules initialized by parent Makefile) all: simple graph newton vpi @@ -25,7 +25,7 @@ graph: @echo "Building cuGraph library from submodule..." @if [ -d "external/cugraph" ]; then \ cd external/cugraph && \ - ./build.sh libcugraph --skip_cpp_tests -n && \ + ./build.sh libcugraph --skip_cpp_tests && \ echo "Building graph apps..." && \ cd ../.. && \ export CUGRAPH_ROOT="$(shell pwd)/external/cugraph" && \ @@ -44,32 +44,34 @@ graph: # Newton apps (Newton submodule - Python-based) newton: - @echo "Setting up Newton apps..." - chmod +x newton/setup_newton.sh newton/newton_diffsim_ball newton/newton_robot_cartpole newton/newton_mpm_granular - @echo "Newton wrapper scripts ready. Virtual environment will be created on first run." + @echo "Newton apps ready (Python-based, no build needed)" -# VPI apps (VPI library from system - optional) -# NOTE: VPI requires system installation. Run setup_vpi.sh with sudo if VPI is not installed. +# VPI apps (VPI library from system - required) vpi: - @if command -v vpi-config &> /dev/null; then \ - echo "Building VPI apps..."; \ - mkdir -p vpi/vpi_background_subtractor/build && \ - cd vpi/vpi_background_subtractor/build && \ - cmake .. && $(MAKE) && \ - cd ../../.. && \ - mkdir -p vpi/vpi_orb_feature_detector/build && \ - cd vpi/vpi_orb_feature_detector/build && \ - cmake .. && $(MAKE) && \ - cd ../../.. && \ - mkdir -p vpi/vpi_stereo_disparity/build && \ - cd vpi/vpi_stereo_disparity/build && \ - cmake .. && $(MAKE); \ - else \ - echo "WARNING: VPI not installed - skipping VPI apps"; \ - echo " To install VPI, run: sudo bash setup_vpi.sh"; \ - fi + @echo "Searching for VPI installation..." + @VPI_ROOT=$$(find /opt/nvidia -maxdepth 1 -name "vpi*" -type d 2>/dev/null | head -1); \ + if [ -z "$$VPI_ROOT" ]; then \ + echo "ERROR: VPI not found in /opt/nvidia/"; \ + echo "Please install VPI from NVIDIA"; \ + exit 1; \ + fi; \ + echo "Found VPI at $$VPI_ROOT"; \ + export VPI_ROOT=$$VPI_ROOT; \ + mkdir -p vpi/vpi_background_subtractor/build && \ + cd vpi/vpi_background_subtractor/build && \ + cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) && \ + cd ../../.. && \ + mkdir -p vpi/vpi_orb_feature_detector/build && \ + cd vpi/vpi_orb_feature_detector/build && \ + cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) && \ + cd ../../.. && \ + mkdir -p vpi/vpi_stereo_disparity/build && \ + cd vpi/vpi_stereo_disparity/build && \ + cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) clean: + # Clean bin directory + -rm -rf bin # Clean simple apps -$(MAKE) -C cufft/cufft_3d_c2c clean -$(MAKE) -C cufft/cufft_lto_r2c_c2r clean diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile index d846333d2..6a1997865 100644 --- a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile +++ b/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile @@ -1,9 +1,24 @@ EXECUTABLE := cufft_lto_r2c_c2r_scalable -CUFILES := -CCFILES := cufft_lto_r2c_c2r_scalable.cpp +CUFILES := r2c_c2r_reference.cu +CCFILES := cufft_lto_r2c_c2r_scalable.cpp common.cpp ADDITIONAL_LIBS := -lcufft OMIT_CUTIL_LIB := 1 OMIT_SHRUTIL_LIB := 1 SM_VERSIONS := 75 80 90 +# LTO fatbin generation variables +CUDA_LTO_ARCH ?= 75 +BIN2C ?= $(CUDA_INSTALL_PATH)/bin/bin2c +FATBIN_HEADER := r2c_c2r_lto_callback_device_fatbin.h +NVCC_KEEP_CLEAN := $(FATBIN_HEADER) r2c_c2r_lto_callback_device.fatbin + include ../../../common/common.mk + +# Generate fatbin header (custom rule added AFTER common.mk) +$(FATBIN_HEADER): r2c_c2r_lto_callback_device.cu + @echo "Generating LTO fatbin header..." + @$(CUDA_INSTALL_PATH)/bin/nvcc --std=c++11 --generate-code arch=compute_$(CUDA_LTO_ARCH),code=lto_$(CUDA_LTO_ARCH) -dc -fatbin $< -o r2c_c2r_lto_callback_device.fatbin + @$(BIN2C) --name window_callback --type longlong r2c_c2r_lto_callback_device.fatbin > $@ + +# Make cpp source depend on fatbin header so it gets built first +cufft_lto_r2c_c2r_scalable.cpp: $(FATBIN_HEADER) diff --git a/src/cuda/H100/generate_large_ppm.py b/src/cuda/H100/generate_large_ppm.py new file mode 100755 index 000000000..571007641 --- /dev/null +++ b/src/cuda/H100/generate_large_ppm.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Generate large PPM image files for recursiveGaussian benchmark. +Creates synthetic test images at various resolutions. +""" + +import sys +import random + +def generate_ppm(width, height, filename): + """Generate a PPM P6 (binary RGB) image file.""" + + print(f"Generating {width}x{height} PPM image...") + + # Calculate sizes + pixel_count = width * height + rgb_data_size = pixel_count * 3 + + with open(filename, 'wb') as f: + # Write ASCII header + header = f"P6\n# Generated test image for recursiveGaussian\n{width} {height}\n255\n" + f.write(header.encode('ascii')) + + # Generate RGB data in chunks to avoid memory issues + chunk_size = 1024 * 1024 # 1MB chunks + bytes_written = 0 + + print(f"Writing {rgb_data_size / (1024*1024):.1f} MB of RGB data...") + + while bytes_written < rgb_data_size: + # Generate chunk of random RGB values + remaining = rgb_data_size - bytes_written + current_chunk_size = min(chunk_size, remaining) + + # Create gradient pattern (more interesting than pure random) + chunk_data = bytearray() + for i in range(current_chunk_size // 3): + pixel_idx = (bytes_written // 3) + i + row = pixel_idx // width + col = pixel_idx % width + + # Create a gradient pattern + r = (col * 255 // width) & 0xFF + g = (row * 255 // height) & 0xFF + b = ((row + col) * 255 // (width + height)) & 0xFF + + chunk_data.extend([r, g, b]) + + f.write(chunk_data) + bytes_written += len(chunk_data) + + # Progress indicator + progress = (bytes_written / rgb_data_size) * 100 + if bytes_written % (10 * 1024 * 1024) < chunk_size: # Every ~10MB + print(f" Progress: {progress:.1f}%") + + # Get file size + import os + file_size = os.path.getsize(filename) + + print(f"✓ Created {filename}") + print(f" Size: {file_size / (1024*1024):.1f} MB") + print(f" Dimensions: {width}x{height}") + print() + +if __name__ == "__main__": + if len(sys.argv) > 1: + # Custom size from command line + if len(sys.argv) != 4: + print("Usage: generate_large_ppm.py ") + print(" or: generate_large_ppm.py (generates standard sizes)") + sys.exit(1) + + width = int(sys.argv[1]) + height = int(sys.argv[2]) + filename = sys.argv[3] + generate_ppm(width, height, filename) + else: + # Generate standard test sizes + import os + script_dir = os.path.dirname(os.path.abspath(__file__)) + base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/H100/recursiveGaussian/data") + + # Create directory if it doesn't exist + os.makedirs(base_dir, exist_ok=True) + + sizes = [ + (768, 768, f"{base_dir}/teapot768.ppm"), + (1024, 1024, f"{base_dir}/teapot1024.ppm"), + # (2048, 2048, f"{base_dir}/teapot2048.ppm"), # Uncomment for 4K + ] + + for width, height, filename in sizes: + generate_ppm(width, height, filename) + + print("All images generated successfully!") diff --git a/src/cuda/H100/generate_large_signal.py b/src/cuda/H100/generate_large_signal.py new file mode 100755 index 000000000..4c5644298 --- /dev/null +++ b/src/cuda/H100/generate_large_signal.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +""" +Generate large signal files for dwtHaar1D benchmark. +Creates synthetic signal data at various sizes (powers of 2). +""" + +import sys +import math + +def generate_signal(size_power, filename, epsilon=0.001): + """ + Generate a signal file with 2^size_power elements. + + Args: + size_power: Power of 2 (e.g., 24 for 2^24 = 16,777,216 elements) + filename: Output filename + epsilon: Epsilon value for header (precision parameter) + """ + + num_elements = 2 ** size_power + + print(f"Generating signal file with {num_elements:,} elements (2^{size_power})...") + print("This will take a few minutes...") + + with open(filename, 'w') as f: + # Write header (epsilon value as comment) + f.write(f"# {epsilon}\n") + + # Generate and write signal values + # Using a simple synthetic signal (sine wave + noise) + chunk_size = 100000 # Write in chunks + values_written = 0 + + while values_written < num_elements: + chunk_values = [] + + for i in range(min(chunk_size, num_elements - values_written)): + idx = values_written + i + + # Create a synthetic signal: combination of multiple frequencies + # This creates a more realistic signal than pure random + t = idx / num_elements # Normalized time 0 to 1 + + # Multiple frequency components + value = ( + math.sin(2 * math.pi * 5 * t) * 0.5 + # 5 Hz + math.sin(2 * math.pi * 13 * t) * 0.3 + # 13 Hz + math.sin(2 * math.pi * 31 * t) * 0.2 # 31 Hz + ) + + chunk_values.append(f"{value:.6f}") + + # Write chunk (space-separated) + f.write(" ".join(chunk_values)) + f.write(" ") + + values_written += len(chunk_values) + + # Progress indicator + progress = (values_written / num_elements) * 100 + if progress % 25 < (100 * chunk_size / num_elements): + print(f"Progress: {progress:.1f}%") + + f.write("\n") + + # Get file size + import os + file_size = os.path.getsize(filename) + + print(f"\n✓ Generated {filename}") + print(f" Elements: {num_elements:,} (2^{size_power})") + print(f" File size: {file_size / (1024*1024):.1f} MB") + print() + +if __name__ == "__main__": + if len(sys.argv) > 1: + # Custom size from command line + if len(sys.argv) != 3: + print("Usage: generate_large_signal.py ") + print(" Example: generate_large_signal.py 24 signal_2_24.dat") + print(" or: generate_large_signal.py (generates standard sizes)") + sys.exit(1) + + size_power = int(sys.argv[1]) + filename = sys.argv[2] + generate_signal(size_power, filename) + else: + # Generate standard test sizes + import os + script_dir = os.path.dirname(os.path.abspath(__file__)) + base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/H100/dwtHaar1D/data") + + # Create directory if it doesn't exist + os.makedirs(base_dir, exist_ok=True) + + sizes = [ + (20, f"{base_dir}/signal_2_20.dat"), # 2^20 = 1,048,576 + (22, f"{base_dir}/signal_2_22.dat"), # 2^22 = 4,194,304 + (24, f"{base_dir}/signal_2_24.dat"), # 2^24 = 16,777,216 + ] + + for power, filename in sizes: + generate_signal(power, filename) + + print("All signal files generated successfully!") diff --git a/src/cuda/H100/get_dwt_data.sh b/src/cuda/H100/get_dwt_data.sh index 40e845a4e..a4c1dfe17 100755 --- a/src/cuda/H100/get_dwt_data.sh +++ b/src/cuda/H100/get_dwt_data.sh @@ -1,9 +1,28 @@ #!/bin/bash # Generate random signal data for DWT -DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/dwtHaar1D" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/dwtHaar1D/data" mkdir -p "$DATA_DIR" +# Link gold files and signal files from cuda-samples +CUDA_SAMPLES_DWT="$SCRIPT_DIR/../cuda-samples/Samples/5_Domain_Specific/dwtHaar1D/data" + +if [ -d "$CUDA_SAMPLES_DWT" ]; then + echo "Linking reference data from cuda-samples..." + # Link gold files (rename to match yml expectations) + ln -sf "$CUDA_SAMPLES_DWT/regression_2_18.gold.dat" "$DATA_DIR/dat_regression_2_18_gold" + ln -sf "$CUDA_SAMPLES_DWT/regression_2_14.gold.dat" "$DATA_DIR/dat_regression_2_14_gold" + ln -sf "$CUDA_SAMPLES_DWT/regression.gold.dat" "$DATA_DIR/dat_regression_gold" + + # Link signal files from cuda-samples + ln -sf "$CUDA_SAMPLES_DWT/signal_2_18.dat" "$DATA_DIR/signal_2_18" + ln -sf "$CUDA_SAMPLES_DWT/signal_2_14.dat" "$DATA_DIR/signal_2_14" + ln -sf "$CUDA_SAMPLES_DWT/signal.dat" "$DATA_DIR/signal" + + echo "Linked reference data from cuda-samples" +fi + # Generate random signal files (using dd for portability - no numpy required) for size in 512 1024 4096 16384 65536; do dd if=/dev/urandom of="$DATA_DIR/signal_${size}.dat" bs=4 count=$size status=none 2>/dev/null @@ -13,3 +32,9 @@ done echo "DWT signal data ready" echo "DWT data ready in $DATA_DIR" + +# Generate large signal files using the Python script +if [ -f "$SCRIPT_DIR/generate_large_signal.py" ]; then + echo "Generating large signal files..." + python3 "$SCRIPT_DIR/generate_large_signal.py" +fi diff --git a/src/cuda/H100/get_graph_data.sh b/src/cuda/H100/get_graph_data.sh index 7b2607891..5ab8b3719 100755 --- a/src/cuda/H100/get_graph_data.sh +++ b/src/cuda/H100/get_graph_data.sh @@ -2,7 +2,7 @@ # Download standard graph datasets and generate synthetic graph SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph/data" mkdir -p "$DATA_DIR" # Download karate club (standard benchmark from SuiteSparse Matrix Collection) diff --git a/src/cuda/H100/get_image_data.sh b/src/cuda/H100/get_image_data.sh index 9e29c8fa4..b1ee885cf 100755 --- a/src/cuda/H100/get_image_data.sh +++ b/src/cuda/H100/get_image_data.sh @@ -1,7 +1,7 @@ #!/bin/bash # Generate test images for recursiveGaussian -DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian" +DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian/data" mkdir -p "$DATA_DIR" # Generate test PPM images using Python (portable, no ImageMagick dependency) @@ -28,3 +28,10 @@ print(f"Generated test images in {data_dir}") EOF echo "Image data ready in $DATA_DIR" + +# Generate large test images using the Python script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/generate_large_ppm.py" ]; then + echo "Generating large PPM images..." + python3 "$SCRIPT_DIR/generate_large_ppm.py" +fi diff --git a/src/cuda/H100/get_vpi_data.sh b/src/cuda/H100/get_vpi_data.sh new file mode 100755 index 000000000..044322b28 --- /dev/null +++ b/src/cuda/H100/get_vpi_data.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Link VPI sample data from VPI installation + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DATA_ROOT="$SCRIPT_DIR/../../../data_dirs/cuda/H100" + +# Find VPI installation +VPI_ROOT=$(find /opt/nvidia -maxdepth 1 -name "vpi*" -type d 2>/dev/null | head -1) + +if [ -z "$VPI_ROOT" ]; then + echo "ERROR: VPI installation not found in /opt/nvidia/" + exit 1 +fi + +VPI_ASSETS="$VPI_ROOT/samples/assets" + +if [ ! -d "$VPI_ASSETS" ]; then + echo "ERROR: VPI sample assets not found at $VPI_ASSETS" + exit 1 +fi + +echo "Linking VPI sample data from $VPI_ASSETS..." + +# Create data directory structure +mkdir -p "$DATA_ROOT/vpi" + +# Remove existing data symlink if it exists +rm -rf "$DATA_ROOT/vpi/data" + +# Create symbolic link to VPI assets +ln -s "$VPI_ASSETS" "$DATA_ROOT/vpi/data" + +echo "VPI data linked successfully at $DATA_ROOT/vpi/data -> $VPI_ASSETS" diff --git a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu index f9a4f6029..9a80481cc 100644 --- a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu +++ b/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu @@ -128,11 +128,11 @@ void runTest(int argc, char **argv) char r_fname[256]; const char usage[] = {"\nUsage:\n" " dwtHaar1D --signal= --result= " - "--gold=\n\n" + "[--gold=]\n\n" " Input file containing the signal\n" " Output file storing the result of the wavelet " "decomposition\n" - " Input file containing the reference result of the " + " (Optional) Input file containing the reference result of the " "wavelet decomposition\n" "\nExample:\n" " ./dwtHaar1D\n" @@ -147,19 +147,22 @@ void runTest(int argc, char **argv) findCudaDevice(argc, (const char **)argv); // file names, either specified as cmd line args or use default - if (argc == 4) { + if (argc >= 3) { char *tmp_sfname, *tmp_rfname, *tmp_goldfname; if ((getCmdLineArgumentString(argc, (const char **)argv, "signal", &tmp_sfname) != true) - || (getCmdLineArgumentString(argc, (const char **)argv, "result", &tmp_rfname) != true) - || (getCmdLineArgumentString(argc, (const char **)argv, "gold", &tmp_goldfname) != true)) { + || (getCmdLineArgumentString(argc, (const char **)argv, "result", &tmp_rfname) != true)) { fprintf(stderr, "Invalid input syntax.\n%s", usage); exit(EXIT_FAILURE); } s_fname = sdkFindFilePath(tmp_sfname, argv[0]); - r_gold_fname = sdkFindFilePath(tmp_goldfname, argv[0]); strcpy(r_fname, tmp_rfname); + + // Gold file is optional + if (getCmdLineArgumentString(argc, (const char **)argv, "gold", &tmp_goldfname) == true) { + r_gold_fname = sdkFindFilePath(tmp_goldfname, argv[0]); + } } else { s_fname = sdkFindFilePath("signal.dat", argv[0]); @@ -169,7 +172,11 @@ void runTest(int argc, char **argv) printf("source file = \"%s\"\n", s_fname); printf("reference file = \"%s\"\n", r_fname); - printf("gold file = \"%s\"\n", r_gold_fname); + if (r_gold_fname != NULL) { + printf("gold file = \"%s\"\n", r_gold_fname); + } else { + printf("gold file = (none - validation skipped)\n"); + } // read in signal unsigned int slength = 0; @@ -324,28 +331,26 @@ void runTest(int argc, char **argv) unsigned int len_reference = 0; float *reference = NULL; - if (r_gold_fname == NULL) { - fprintf(stderr, - "Cannot read the file containing the reference result of the " - "wavelet decomposition.\n%s", - usage); + // Validation is optional - only run if gold file is provided + if (r_gold_fname != NULL) { + if (sdkReadFile(r_gold_fname, &reference, &len_reference, false) == true) { + printf("Reading reference result from \"%s\"\n", r_gold_fname); + } + else { + fprintf(stderr, "Failed to read gold file \"%s\"\n", r_gold_fname); + exit(EXIT_FAILURE); + } - exit(EXIT_FAILURE); - } + assert(slength == len_reference); - if (sdkReadFile(r_gold_fname, &reference, &len_reference, false) == true) { - printf("Reading reference result from \"%s\"\n", r_gold_fname); - } - else { - exit(EXIT_FAILURE); + // compare the computed solution and the reference + bResult = (bool)sdkCompareL2fe(reference, odata, slength, 0.001f); + free(reference); + } else { + printf("No gold file provided - skipping validation\n"); + bResult = true; // Mark as success when validation is skipped } - assert(slength == len_reference); - - // compare the computed solution and the reference - bResult = (bool)sdkCompareL2fe(reference, odata, slength, 0.001f); - free(reference); - // free allocated host and device memory checkCudaErrors(cudaFree(d_odata)); checkCudaErrors(cudaFree(d_idata)); @@ -354,9 +359,15 @@ void runTest(int argc, char **argv) free(signal); free(odata); free(s_fname); - free(r_gold_fname); + if (r_gold_fname != NULL) { + free(r_gold_fname); + } - printf(bResult ? "Test success!\n" : "Test failure!\n"); + if (r_gold_fname != NULL) { + printf(bResult ? "Test success!\n" : "Test failure!\n"); + } else { + printf("Processing complete (validation skipped)\n"); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py b/src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py new file mode 100644 index 000000000..0ae69119e --- /dev/null +++ b/src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py @@ -0,0 +1,284 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. + +########################################################################### +# Example Diffsim Ball +# +# Shows how to use Newton to optimize the initial velocity of a particle +# such that it bounces off the wall and floor in order to hit a target. +# +# This example uses the built-in wp.Tape() object to compute gradients of +# the distance to target (loss) w.r.t the initial velocity, followed by +# a simple gradient-descent optimization step. +# +# Command: python -m newton.examples diffsim_ball +# +########################################################################### +import numpy as np +import warp as wp + +import newton +import newton.examples +from newton.tests.unittest_utils import assert_np_equal +from newton.utils import bourke_color_map + + +@wp.kernel +def loss_kernel(pos: wp.array(dtype=wp.vec3), target: wp.vec3, loss: wp.array(dtype=float)): + # distance to target + delta = pos[0] - target + loss[0] = wp.dot(delta, delta) + + +@wp.kernel +def step_kernel(x: wp.array(dtype=wp.vec3), grad: wp.array(dtype=wp.vec3), alpha: float): + tid = wp.tid() + + # gradient descent step + x[tid] = x[tid] - grad[tid] * alpha + + +class Example: + def __init__(self, viewer, args): + # setup simulation parameters first + self.fps = 60 + self.frame = 0 + self.frame_dt = 1.0 / self.fps + self.sim_steps = 36 + self.sim_substeps = 8 + self.sim_dt = self.frame_dt / self.sim_substeps + + self.verbose = args.verbose + + self.train_iter = 0 + self.train_rate = 0.02 + self.target = (0.0, -2.0, 1.5) + self.loss = wp.zeros(1, dtype=wp.float32, requires_grad=True) + self.loss_history = [] + + self.viewer = viewer + self.viewer.show_particles = True + + # setup simulation scene + scene = newton.ModelBuilder(up_axis=newton.Axis.Z) + + scene.add_particle(pos=wp.vec3(0.0, -0.5, 1.0), vel=wp.vec3(0.0, 5.0, -5.0), mass=1.0) + + # add wall and ground plane + ke = 1.0e4 + kf = 0.0 + kd = 1.0e1 + mu = 0.2 + + scene.add_shape_box( + body=-1, + xform=wp.transform(wp.vec3(0.0, 2.0, 1.0), wp.quat_identity()), + hx=1.0, + hy=0.25, + hz=1.0, + cfg=newton.ModelBuilder.ShapeConfig(ke=ke, kf=kf, kd=kd, mu=mu), + ) + + scene.add_ground_plane(cfg=newton.ModelBuilder.ShapeConfig(ke=ke, kf=kf, kd=kd, mu=mu)) + + # finalize model + # use `requires_grad=True` to create a model for differentiable simulation + self.model = scene.finalize(requires_grad=True) + + self.model.soft_contact_ke = ke + self.model.soft_contact_kf = kf + self.model.soft_contact_kd = kd + self.model.soft_contact_mu = mu + self.model.soft_contact_restitution = 1.0 + + self.solver = newton.solvers.SolverSemiImplicit(self.model) + + # allocate sim states, initialize control and one-shot contacts (valid for simple collisions against constant plane) + self.states = [self.model.state() for _ in range(self.sim_steps * self.sim_substeps + 1)] + self.control = self.model.control() + + # Create collision pipeline (requires_grad for differentiable simulation) + self.collision_pipeline = newton.CollisionPipeline( + self.model, + broad_phase="explicit", + soft_contact_margin=10.0, + requires_grad=True, + ) + self.contacts = self.collision_pipeline.contacts() + self.collision_pipeline.collide(self.states[0], self.contacts) + + self.viewer.set_model(self.model) + + # capture forward/backward passes + self.capture() + + def capture(self): + # if wp.get_device().is_cuda: + # with wp.ScopedCapture() as capture: + # self.forward_backward() + # self.graph = capture.graph + # else: + self.graph = None + + def forward_backward(self): + self.tape = wp.Tape() + with self.tape: + self.forward() + self.tape.backward(self.loss) + + def forward(self): + # run simulation loop + for sim_step in range(self.sim_steps): + self.simulate(sim_step) + + # compute loss on final state + wp.launch(loss_kernel, dim=1, inputs=[self.states[-1].particle_q, self.target, self.loss]) + + return self.loss + + def simulate(self, sim_step): + for i in range(self.sim_substeps): + t = sim_step * self.sim_substeps + i + self.states[t].clear_forces() + self.solver.step(self.states[t], self.states[t + 1], self.control, self.contacts, self.sim_dt) + + def step(self): + if self.graph: + wp.capture_launch(self.graph) + else: + self.forward_backward() + + x = self.states[0].particle_qd + + if self.verbose: + print(f"Train iter: {self.train_iter} Loss: {self.loss}") + print(f" x: {x} g: {x.grad}") + + # gradient descent step + wp.launch(step_kernel, dim=len(x), inputs=[x, x.grad, self.train_rate]) + + # clear grads for next iteration + self.tape.zero() + + self.train_iter += 1 + self.loss_history.append(self.loss.numpy()[0]) + + def test_final(self): + x_grad_numeric, x_grad_analytic = self.check_grad() + assert_np_equal(x_grad_numeric, x_grad_analytic, tol=5e-2) + assert all(np.array(self.loss_history) < 10.0) + # skip the last loss because there could be some bouncing around the optimum + assert all(np.diff(self.loss_history[:-1]) < -1e-3) + + def render(self): + if self.viewer.is_paused(): + self.viewer.begin_frame(self.viewer.time) + self.viewer.end_frame() + return + + if self.frame > 0 and self.train_iter % 16 != 0: + return + + # draw trajectory + traj_verts = [self.states[0].particle_q.numpy()[0].tolist()] + + for i in range(self.sim_steps + 1): + state = self.states[i * self.sim_substeps] + traj_verts.append(state.particle_q.numpy()[0].tolist()) + + self.viewer.begin_frame(self.frame * self.frame_dt) + self.viewer.log_scalar("/loss", self.loss.numpy()[0]) + self.viewer.log_state(state) + self.viewer.log_contacts(self.contacts, state) + self.viewer.log_shapes( + "/target", + newton.GeoType.BOX, + (0.1, 0.1, 0.1), + wp.array([wp.transform(self.target, wp.quat_identity())], dtype=wp.transform), + wp.array([wp.vec3(0.0, 0.0, 0.0)], dtype=wp.vec3), + ) + self.viewer.log_lines( + f"/traj_{self.train_iter - 1}", + wp.array(traj_verts[0:-1], dtype=wp.vec3), + wp.array(traj_verts[1:], dtype=wp.vec3), + bourke_color_map(0.0, 7.0, self.loss.numpy()[0]), + ) + self.viewer.end_frame() + + self.frame += 1 + + def check_grad(self): + param = self.states[0].particle_qd + + # initial value + x_c = param.numpy().flatten() + + # compute numeric gradient + x_grad_numeric = np.zeros_like(x_c) + + for i in range(len(x_c)): + eps = 1.0e-3 + + step = np.zeros_like(x_c) + step[i] = eps + + x_1 = x_c + step + x_0 = x_c - step + + param.assign(x_1) + l_1 = self.forward().numpy()[0] + + param.assign(x_0) + l_0 = self.forward().numpy()[0] + + dldx = (l_1 - l_0) / (eps * 2.0) + + x_grad_numeric[i] = dldx + + # reset initial state + param.assign(x_c) + + # compute analytic gradient + tape = wp.Tape() + with tape: + l = self.forward() + + tape.backward(l) + + x_grad_analytic = param.grad.numpy()[0].copy() + + print(f"numeric grad: {x_grad_numeric}") + print(f"analytic grad: {x_grad_analytic}") + + tape.zero() + + return x_grad_numeric, x_grad_analytic + + @staticmethod + def create_parser(): + parser = newton.examples.create_parser() + parser.add_argument( + "--verbose", action="store_true", help="Print out additional status messages during execution." + ) + return parser + + +if __name__ == "__main__": + parser = Example.create_parser() + viewer, args = newton.examples.init(parser) + + example = Example(viewer, args) + example.check_grad() + newton.examples.run(example, args) diff --git a/src/cuda/H100/newton/newton_diffsim_ball b/src/cuda/H100/newton/newton_diffsim_ball deleted file mode 100755 index 7d501253b..000000000 --- a/src/cuda/H100/newton/newton_diffsim_ball +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# Wrapper for Newton diffsim ball benchmark - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/setup_newton.sh" - -# Run Newton example -python -m newton.examples.diffsim.example_diffsim_ball --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/newton_mpm_granular b/src/cuda/H100/newton/newton_mpm_granular deleted file mode 100755 index ae8cbce57..000000000 --- a/src/cuda/H100/newton/newton_mpm_granular +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# Wrapper for Newton MPM granular benchmark - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/setup_newton.sh" - -# Run Newton example -python -m newton.examples.mpm.example_mpm_granular --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/newton_robot_cartpole b/src/cuda/H100/newton/newton_robot_cartpole deleted file mode 100755 index 73c6dd9f4..000000000 --- a/src/cuda/H100/newton/newton_robot_cartpole +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# Wrapper for Newton robot cartpole benchmark - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/setup_newton.sh" - -# Run Newton example -python -m newton.examples.robot.example_robot_cartpole --viewer null --num-frames 5 diff --git a/src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py b/src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py new file mode 100644 index 000000000..485c865f0 --- /dev/null +++ b/src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py @@ -0,0 +1,206 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. + +########################################################################### +# Example Robot Cartpole +# +# Shows how to set up a simulation of a rigid-body cartpole articulation +# from a USD stage using newton.ModelBuilder.add_usd(). +# +# Command: python -m newton.examples robot_cartpole --world-count 100 +# +########################################################################### + +import warp as wp + +import newton +import newton.examples + + +class Example: + def __init__(self, viewer, args): + self.fps = 60 + self.frame_dt = 1.0 / self.fps + self.sim_time = 0.0 + self.sim_substeps = 10 + self.sim_dt = self.frame_dt / self.sim_substeps + + self.world_count = args.world_count + + self.viewer = viewer + + cartpole = newton.ModelBuilder() + newton.solvers.SolverMuJoCo.register_custom_attributes(cartpole) + cartpole.default_shape_cfg.density = 100.0 + cartpole.default_joint_cfg.armature = 0.1 + cartpole.default_body_armature = 0.1 + + cartpole.add_usd( + newton.examples.get_asset("cartpole.usda"), + enable_self_collisions=False, + collapse_fixed_joints=True, + ) + # set initial joint positions + cartpole.joint_q[-3:] = [0.0, 0.3, 0.0] + + builder = newton.ModelBuilder() + builder.replicate(cartpole, self.world_count, spacing=(1.0, 2.0, 0.0)) + + # finalize model + self.model = builder.finalize() + + self.solver = newton.solvers.SolverMuJoCo(self.model) + # self.solver = newton.solvers.SolverSemiImplicit(self.model, joint_attach_ke=1600.0, joint_attach_kd=20.0) + # self.solver = newton.solvers.SolverFeatherstone(self.model) + + self.state_0 = self.model.state() + self.state_1 = self.model.state() + self.control = self.model.control() + # we do not need to evaluate contacts for this example + self.contacts = None + + # Evaluating forward kinematics is needed only for maximal-coordinate solvers + newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.state_0) + + self.viewer.set_model(self.model) + self.viewer.set_world_offsets((0.0, 0.0, 0.0)) + + # Set camera to view all the cartpoles + self.viewer.set_camera( + pos=wp.vec3(7.3, -14.0, 2.3), + pitch=-5.0, + yaw=-225.0, + ) + if hasattr(self.viewer, "camera") and hasattr(self.viewer.camera, "fov"): + self.viewer.camera.fov = 90.0 + + self.capture() + + def capture(self): + self.graph = None + # if wp.get_device().is_cuda: + # with wp.ScopedCapture() as capture: + # self.simulate() + # self.graph = capture.graph + + def simulate(self): + for _ in range(self.sim_substeps): + self.state_0.clear_forces() + + # apply forces to the model for picking, wind, etc + self.viewer.apply_forces(self.state_0) + + self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) + + # swap states + self.state_0, self.state_1 = self.state_1, self.state_0 + + def step(self): + if self.graph: + wp.capture_launch(self.graph) + else: + self.simulate() + + self.sim_time += self.frame_dt + + def render(self): + self.viewer.begin_frame(self.sim_time) + self.viewer.log_state(self.state_0) + self.viewer.end_frame() + + def test_final(self): + num_bodies_per_world = self.model.body_count // self.world_count + newton.examples.test_body_state( + self.model, + self.state_0, + "cart is at ground level and has correct orientation", + lambda q, qd: q[2] == 0.0 and newton.math.vec_allclose(q.q, wp.quat_identity()), + indices=[i * num_bodies_per_world for i in range(self.world_count)], + ) + newton.examples.test_body_state( + self.model, + self.state_0, + "cart only moves along y direction", + lambda q, qd: qd[0] == 0.0 + and abs(qd[1]) > 0.05 + and qd[2] == 0.0 + and wp.length_sq(wp.spatial_bottom(qd)) == 0.0, + indices=[i * num_bodies_per_world for i in range(self.world_count)], + ) + newton.examples.test_body_state( + self.model, + self.state_0, + "pole1 only has y-axis linear velocity and x-axis angular velocity", + lambda q, qd: qd[0] == 0.0 + and abs(qd[1]) > 0.05 + and qd[2] == 0.0 + and abs(qd[3]) > 0.3 + and qd[4] == 0.0 + and qd[5] == 0.0, + indices=[i * num_bodies_per_world + 1 for i in range(self.world_count)], + ) + newton.examples.test_body_state( + self.model, + self.state_0, + "pole2 only has yz-plane linear velocity and x-axis angular velocity", + lambda q, qd: qd[0] == 0.0 + and abs(qd[1]) > 0.05 + and abs(qd[2]) > 0.05 + and abs(qd[3]) > 0.2 + and qd[4] == 0.0 + and qd[5] == 0.0, + indices=[i * num_bodies_per_world + 2 for i in range(self.world_count)], + ) + qd = self.state_0.body_qd.numpy() + world0_cart_vel = wp.spatial_vector(*qd[0]) + world0_pole1_vel = wp.spatial_vector(*qd[1]) + world0_pole2_vel = wp.spatial_vector(*qd[2]) + newton.examples.test_body_state( + self.model, + self.state_0, + "cart velocities match across worlds", + lambda q, qd: newton.math.vec_allclose(qd, world0_cart_vel), + indices=[i * num_bodies_per_world for i in range(self.world_count)], + ) + newton.examples.test_body_state( + self.model, + self.state_0, + "pole1 velocities match across worlds", + lambda q, qd: newton.math.vec_allclose(qd, world0_pole1_vel), + indices=[i * num_bodies_per_world + 1 for i in range(self.world_count)], + ) + newton.examples.test_body_state( + self.model, + self.state_0, + "pole2 velocities match across worlds", + lambda q, qd: newton.math.vec_allclose(qd, world0_pole2_vel, atol=1e-6), + indices=[i * num_bodies_per_world + 2 for i in range(self.world_count)], + ) + + @staticmethod + def create_parser(): + parser = newton.examples.create_parser() + newton.examples.add_world_count_arg(parser) + parser.set_defaults(world_count=100) + return parser + + +if __name__ == "__main__": + parser = Example.create_parser() + viewer, args = newton.examples.init(parser) + + example = Example(viewer, args) + + newton.examples.run(example, args) diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt b/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt index 5ba6d5c46..bb6a1f11a 100644 --- a/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt +++ b/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt @@ -1,12 +1,38 @@ -cmake_minimum_required(VERSION 3.18) -project(vpi_background_subtractor CXX) - -# Find VPI library (optional) -find_package(vpi QUIET) - -if(vpi_FOUND) - add_executable(vpi_background_subtractor main.cpp) - target_link_libraries(vpi_background_subtractor PRIVATE vpi) -else() - message(WARNING "VPI not found - skipping vpi_background_subtractor") -endif() +# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.22) + +project(vpi_background_subtractor) + +set(CMAKE_CXX_STANDARD 11) + +find_package(vpi 4.0 REQUIRED) +find_package(OpenCV REQUIRED) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} vpi opencv_core + opencv_imgproc opencv_imgcodecs opencv_videoio) diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp index f7de2fbaa..2f36df519 100644 --- a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp +++ b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp @@ -136,9 +136,9 @@ int main(int argc, char *argv[]) } cv::VideoWriter bgimageVideo("bgimage_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height)); - if (!bgimageVideo.isOpened()) + if (!outVideo.isOpened()) { - throw std::runtime_error("Can't create background image video"); + throw std::runtime_error("Can't create output video"); } // Fetch a new frame until video ends diff --git a/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt b/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt index 548e8fba6..f42b80240 100644 --- a/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt +++ b/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt @@ -1,11 +1,35 @@ -cmake_minimum_required(VERSION 3.18) -project(vpi_orb_feature_detector CXX) +# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -find_package(vpi QUIET) +cmake_minimum_required(VERSION 3.22) -if(vpi_FOUND) - add_executable(vpi_orb_feature_detector main.cpp) - target_link_libraries(vpi_orb_feature_detector PRIVATE vpi) -else() - message(WARNING "VPI not found - skipping vpi_orb_feature_detector") -endif() +project(vpi_orb_feature_detector) + +find_package(vpi 4.0 REQUIRED) +find_package(OpenCV REQUIRED) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} vpi opencv_imgproc opencv_core opencv_imgcodecs opencv_features2d) diff --git a/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt b/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt index 927fd8d08..3597108ad 100644 --- a/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt +++ b/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt @@ -1,11 +1,35 @@ -cmake_minimum_required(VERSION 3.18) -project(vpi_stereo_disparity CXX) +# Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -find_package(vpi QUIET) +cmake_minimum_required(VERSION 3.22) -if(vpi_FOUND) - add_executable(vpi_stereo_disparity main.cpp) - target_link_libraries(vpi_stereo_disparity PRIVATE vpi) -else() - message(WARNING "VPI not found - skipping vpi_stereo_disparity") -endif() +project(vpi_stereo_disparity) + +find_package(vpi 4.0 REQUIRED) +find_package(OpenCV REQUIRED) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} vpi opencv_imgproc opencv_core opencv_imgcodecs) diff --git a/src/cuda/common/common.mk b/src/cuda/common/common.mk index cd5f3cefc..14885532b 100644 --- a/src/cuda/common/common.mk +++ b/src/cuda/common/common.mk @@ -21,7 +21,9 @@ .SUFFIXES : .cu .cu_dbg.o .c_dbg.o .cpp_dbg.o .cu_rel.o .c_rel.o .cpp_rel.o .cubin .ptx INCLUDES += -I$(NVIDIA_COMPUTE_SDK_LOCATION)/../4.2/C/common/inc -ADDITIONAL_LIBS += -L$(NVIDIA_COMPUTE_SDK_LOCATION)/../4.2/C/lib -lcutil_x86_64 +ifneq ($(OMIT_CUTIL_LIB),1) + ADDITIONAL_LIBS += -L$(NVIDIA_COMPUTE_SDK_LOCATION)/../4.2/C/lib -lcutil_x86_64 +endif # Add new SM Versions here as devices with new Compute Capability are released SM_VERSIONS := 70 75 @@ -56,6 +58,9 @@ BINDIR ?= $(ROOTBINDIR)/$(OSLOWER) ROOTOBJDIR ?= obj LIBDIR ?= $(ROOTDIR)/../lib +# Set CUDA_VERSION_MAJOR if not already set +CUDA_VERSION_MAJOR ?= 12 + ifeq ($(shell test ${CUDA_VERSION_MAJOR} -lt 5; echo $$?), 0) LIBDIRSDK := $(NVIDIA_COMPUTE_SDK_LOCATION)/C/lib COMMONDIR := $(NVIDIA_COMPUTE_SDK_LOCATION)/C/common From fb90e99d9f1688c6e57688944bca639a753b5883 Mon Sep 17 00:00:00 2001 From: Anunalla Date: Wed, 25 Mar 2026 12:24:53 +0000 Subject: [PATCH 05/11] =?UTF-8?q?=C3=B8data=20folder=20organization=20clea?= =?UTF-8?q?nup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Makefile | 9 +++++++++ src/cuda/H100/get_dwt_data.sh | 16 ++++++++-------- src/cuda/H100/get_graph_data.sh | 2 +- src/cuda/H100/get_image_data.sh | 17 +++++++++++------ src/cuda/H100/get_vpi_data.sh | 22 ++++++++++++---------- src/cuda/H100/newton/setup_newton.sh | 2 +- 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/Makefile b/src/Makefile index 714e89d5d..1da014f11 100644 --- a/src/Makefile +++ b/src/Makefile @@ -549,6 +549,13 @@ clean_cutlass: H100: mkdir -p $(BINDIR)/$(BINSUBDIR)/ # Initialize and update submodules (like cutlass pattern) + # Clean up any corrupted submodule directories (check if .git exists) + @for dir in cuda/cuda-samples cuda/H100/external/cugraph cuda/H100/external/newton; do \ + if [ -d $$dir ] && [ ! -d $$dir/.git ]; then \ + echo "Removing corrupted submodule directory: $$dir"; \ + rm -rf $$dir; \ + fi; \ + done git submodule sync && git submodule update --init --recursive # Build simple apps (cuFFT, cuSolver, image processing) $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 simple @@ -557,6 +564,8 @@ H100: # cp cuda/H100/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ # Build Newton apps (Newton submodule) cp -r cuda/H100/newton $(BINDIR)/$(BINSUBDIR)/ + mkdir -p $(BINDIR)/$(BINSUBDIR)/external + cp -r cuda/H100/external/newton $(BINDIR)/$(BINSUBDIR)/external/ bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball diff --git a/src/cuda/H100/get_dwt_data.sh b/src/cuda/H100/get_dwt_data.sh index a4c1dfe17..d958081ad 100755 --- a/src/cuda/H100/get_dwt_data.sh +++ b/src/cuda/H100/get_dwt_data.sh @@ -2,23 +2,23 @@ # Generate random signal data for DWT SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/dwtHaar1D/data" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/dwtHaar1D/data/" mkdir -p "$DATA_DIR" # Link gold files and signal files from cuda-samples -CUDA_SAMPLES_DWT="$SCRIPT_DIR/../cuda-samples/Samples/5_Domain_Specific/dwtHaar1D/data" +CUDA_SAMPLES_DWT="$SCRIPT_DIR/../cuda-samples/Samples/5_Domain_Specific/dwtHaar1D/" if [ -d "$CUDA_SAMPLES_DWT" ]; then echo "Linking reference data from cuda-samples..." # Link gold files (rename to match yml expectations) - ln -sf "$CUDA_SAMPLES_DWT/regression_2_18.gold.dat" "$DATA_DIR/dat_regression_2_18_gold" - ln -sf "$CUDA_SAMPLES_DWT/regression_2_14.gold.dat" "$DATA_DIR/dat_regression_2_14_gold" - ln -sf "$CUDA_SAMPLES_DWT/regression.gold.dat" "$DATA_DIR/dat_regression_gold" + ln -sf "$CUDA_SAMPLES_DWT/data/regression_2_18.gold.dat" "$DATA_DIR/regression_2_18.gold.dat" + ln -sf "$CUDA_SAMPLES_DWT/data/regression_2_14.gold.dat" "$DATA_DIR/regression_2_14.gold.dat" + ln -sf "$CUDA_SAMPLES_DWT/data/regression.gold.dat" "$DATA_DIR/regression.gold.dat" # Link signal files from cuda-samples - ln -sf "$CUDA_SAMPLES_DWT/signal_2_18.dat" "$DATA_DIR/signal_2_18" - ln -sf "$CUDA_SAMPLES_DWT/signal_2_14.dat" "$DATA_DIR/signal_2_14" - ln -sf "$CUDA_SAMPLES_DWT/signal.dat" "$DATA_DIR/signal" + ln -sf "$CUDA_SAMPLES_DWT/data/signal_2_18.dat" "$DATA_DIR/signal_2_18.dat" + ln -sf "$CUDA_SAMPLES_DWT/data/signal_2_14.dat" "$DATA_DIR/signal_2_14.dat" + ln -sf "$CUDA_SAMPLES_DWT/data/signal.dat" "$DATA_DIR/signal.dat" echo "Linked reference data from cuda-samples" fi diff --git a/src/cuda/H100/get_graph_data.sh b/src/cuda/H100/get_graph_data.sh index 5ab8b3719..62c4d2397 100755 --- a/src/cuda/H100/get_graph_data.sh +++ b/src/cuda/H100/get_graph_data.sh @@ -2,7 +2,7 @@ # Download standard graph datasets and generate synthetic graph SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph/data" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph/data/" mkdir -p "$DATA_DIR" # Download karate club (standard benchmark from SuiteSparse Matrix Collection) diff --git a/src/cuda/H100/get_image_data.sh b/src/cuda/H100/get_image_data.sh index b1ee885cf..e9fb17cc3 100755 --- a/src/cuda/H100/get_image_data.sh +++ b/src/cuda/H100/get_image_data.sh @@ -1,7 +1,7 @@ #!/bin/bash # Generate test images for recursiveGaussian -DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian/data" +DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian/data/" mkdir -p "$DATA_DIR" # Generate test PPM images using Python (portable, no ImageMagick dependency) @@ -10,20 +10,25 @@ import os import sys def create_gradient_ppm(filename, size): - """Create a simple gradient PPM image""" - with open(filename, 'w') as f: - f.write(f"P3\n{size} {size}\n255\n") + """Create a simple gradient PPM image in P6 (binary) format""" + with open(filename, 'wb') as f: + # Write header in ASCII + header = f"P6\n{size} {size}\n255\n" + f.write(header.encode('ascii')) + # Write pixel data in binary for y in range(size): for x in range(size): # Gradient from black to white val = int((x + y) * 255 / (2 * size)) - f.write(f"{val} {val} {val} ") - f.write("\n") + # Write RGB as 3 bytes (RGBA would need 4th byte) + f.write(bytes([val, val, val])) data_dir = os.path.expanduser(sys.argv[1]) create_gradient_ppm(f"{data_dir}/teapot128.ppm", 128) create_gradient_ppm(f"{data_dir}/teapot256.ppm", 256) create_gradient_ppm(f"{data_dir}/teapot512.ppm", 512) +create_gradient_ppm(f"{data_dir}/teapot768.ppm", 768) +create_gradient_ppm(f"{data_dir}/teapot1024.ppm", 1024) print(f"Generated test images in {data_dir}") EOF diff --git a/src/cuda/H100/get_vpi_data.sh b/src/cuda/H100/get_vpi_data.sh index 044322b28..55ec41fdf 100755 --- a/src/cuda/H100/get_vpi_data.sh +++ b/src/cuda/H100/get_vpi_data.sh @@ -21,13 +21,15 @@ fi echo "Linking VPI sample data from $VPI_ASSETS..." -# Create data directory structure -mkdir -p "$DATA_ROOT/vpi" - -# Remove existing data symlink if it exists -rm -rf "$DATA_ROOT/vpi/data" - -# Create symbolic link to VPI assets -ln -s "$VPI_ASSETS" "$DATA_ROOT/vpi/data" - -echo "VPI data linked successfully at $DATA_ROOT/vpi/data -> $VPI_ASSETS" +# Create data directory structure for each VPI app +for app in vpi_background_subtractor vpi_orb_feature_detector vpi_stereo_disparity; do + # Create parent directory + mkdir -p "$DATA_ROOT/$app" + # Remove existing data directory/symlink if it exists + rm -rf "$DATA_ROOT/$app/data" + # Create symbolic link to VPI assets + ln -sf "$VPI_ASSETS" "$DATA_ROOT/$app/data" + echo " $app/data -> $VPI_ASSETS" +done + +echo "VPI data linked successfully" diff --git a/src/cuda/H100/newton/setup_newton.sh b/src/cuda/H100/newton/setup_newton.sh index 28b213c81..7ec1090f4 100755 --- a/src/cuda/H100/newton/setup_newton.sh +++ b/src/cuda/H100/newton/setup_newton.sh @@ -16,7 +16,7 @@ if [ ! -d "$NEWTON_DIR/newton_venv" ]; then echo "Installing Newton from submodule..." pip install -e "$NEWTON_SUBMODULE" # Install additional dependencies for robot and USD examples - pip install usd-core mujoco-warp + pip install usd-core mujoco-warp numpy warp-lang else echo "WARNING: Newton submodule not found at $NEWTON_SUBMODULE" echo "Run: git submodule update --init --recursive" From 67329548711b1402ec8c73e0a533dddaa4724aae Mon Sep 17 00:00:00 2001 From: Anunalla Date: Wed, 25 Mar 2026 15:29:22 +0000 Subject: [PATCH 06/11] Rename suite folder, remove VPI local copies, use symlink --- .gitignore | 5 +- .gitmodules | 8 +- get_data.sh | 19 +- src/Makefile | 42 +-- .../vpi_background_subtractor/CMakeLists.txt | 38 -- .../vpi/vpi_background_subtractor/main.cpp | 219 ----------- .../vpi_orb_feature_detector/CMakeLists.txt | 35 -- .../vpi/vpi_orb_feature_detector/main.cpp | 289 -------------- .../vpi/vpi_stereo_disparity/CMakeLists.txt | 35 -- .../H100/vpi/vpi_stereo_disparity/main.cpp | 354 ------------------ src/cuda/{H100 => HPC}/Makefile | 21 +- src/cuda/{H100 => HPC}/README.md | 0 .../{H100 => HPC}/cufft/cufft_3d_c2c/Makefile | 0 .../cufft_3d_c2c/cufft_3d_c2c_scalable.cu | 0 .../cufft/cufft_3d_c2c/cufft_utils.h | 0 .../cufft/cufft_lto_r2c_c2r/Makefile | 0 .../cufft/cufft_lto_r2c_c2r/callback_params.h | 0 .../cufft/cufft_lto_r2c_c2r/common.cpp | 0 .../cufft/cufft_lto_r2c_c2r/common.h | 0 .../cufft_lto_r2c_c2r_scalable.cpp | 0 .../cufft/cufft_lto_r2c_c2r/nvrtc_helper.h | 0 .../r2c_c2r_legacy_callback_example.cu | 0 .../r2c_c2r_lto_callback_device.cu | 0 .../r2c_c2r_lto_callback_device.fatbin | Bin 0 -> 2536 bytes .../r2c_c2r_lto_callback_device_fatbin.h | 91 +++++ .../r2c_c2r_lto_nvrtc_callback_example.cpp | 0 .../cufft_lto_r2c_c2r/r2c_c2r_reference.cu | 0 .../cufft_lto_r2c_c2r/r2c_c2r_reference.h | 0 .../cusolver/cusolver_Xgetrf/Makefile | 0 .../cusolver_Xgetrf_scalable.cu | 0 .../cusolver/cusolver_Xgetrf/cusolver_utils.h | 0 .../cusolver/cusolver_ormqr/Makefile | 0 .../cusolver_ormqr/cusolver_ormqr_scalable.cu | 0 .../cusolver/cusolver_ormqr/cusolver_utils.h | 0 src/cuda/{H100 => HPC}/external/cugraph | 0 src/cuda/{H100 => HPC}/external/newton | 0 src/cuda/HPC/find_cugraph_tag.sh | 46 +++ src/cuda/{H100 => HPC}/generate_graph.py | 0 src/cuda/{H100 => HPC}/generate_large_ppm.py | 2 +- .../{H100 => HPC}/generate_large_signal.py | 2 +- src/cuda/{H100 => HPC}/get_dwt_data.sh | 2 +- src/cuda/{H100 => HPC}/get_graph_data.sh | 2 +- src/cuda/{H100 => HPC}/get_image_data.sh | 2 +- src/cuda/{H100 => HPC}/get_vpi_data.sh | 2 +- .../graph/bfs_standalone/CMakeLists.txt | 0 .../graph/bfs_standalone/bfs_standalone.cu | 0 .../graph/mst_standalone/CMakeLists.txt | 0 .../graph/mst_standalone/mst_standalone.cu | 0 .../{H100 => HPC}/image/FDTD3d/FDTD3d.cpp | 0 src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3d.h | 0 .../{H100 => HPC}/image/FDTD3d/FDTD3dGPU.cu | 0 .../{H100 => HPC}/image/FDTD3d/FDTD3dGPU.h | 0 .../image/FDTD3d/FDTD3dGPUKernel.cuh | 0 .../image/FDTD3d/FDTD3dReference.cpp | 0 .../image/FDTD3d/FDTD3dReference.h | 0 src/cuda/{H100 => HPC}/image/FDTD3d/Makefile | 0 .../{H100 => HPC}/image/FDTD3d/exception.h | 0 .../{H100 => HPC}/image/FDTD3d/helper_cuda.h | 0 .../image/FDTD3d/helper_functions.h | 0 .../{H100 => HPC}/image/FDTD3d/helper_image.h | 0 .../image/FDTD3d/helper_string.h | 0 .../{H100 => HPC}/image/FDTD3d/helper_timer.h | 0 .../{H100 => HPC}/image/dwtHaar1D/Makefile | 0 .../image/dwtHaar1D/dwtHaar1D.cu | 0 .../image/dwtHaar1D/dwtHaar1D_kernel.cuh | 0 .../{H100 => HPC}/image/dwtHaar1D/exception.h | 0 .../image/dwtHaar1D/helper_cuda.h | 0 .../image/dwtHaar1D/helper_functions.h | 0 .../image/dwtHaar1D/helper_image.h | 0 .../image/dwtHaar1D/helper_string.h | 0 .../image/dwtHaar1D/helper_timer.h | 0 .../image/recursiveGaussian/Makefile | 0 .../image/recursiveGaussian/exception.h | 0 .../image/recursiveGaussian/helper_cuda.h | 0 .../recursiveGaussian/helper_functions.h | 0 .../image/recursiveGaussian/helper_gl.h | 0 .../image/recursiveGaussian/helper_image.h | 0 .../image/recursiveGaussian/helper_math.h | 0 .../image/recursiveGaussian/helper_string.h | 0 .../image/recursiveGaussian/helper_timer.h | 0 .../recursiveGaussian/recursiveGaussian.cpp | 0 .../recursiveGaussian_cuda.cu | 0 .../recursiveGaussian_kernel.cuh | 0 .../diffsim_ball/example_diffsim_ball.py | 0 .../robot_cartpole/example_robot_cartpole.py | 0 src/cuda/{H100 => HPC}/newton/setup_newton.sh | 0 src/cuda/{H100 => HPC}/setup_vpi.sh | 0 87 files changed, 197 insertions(+), 1017 deletions(-) delete mode 100644 src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt delete mode 100644 src/cuda/H100/vpi/vpi_background_subtractor/main.cpp delete mode 100644 src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt delete mode 100644 src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp delete mode 100644 src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt delete mode 100644 src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp rename src/cuda/{H100 => HPC}/Makefile (71%) rename src/cuda/{H100 => HPC}/README.md (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_3d_c2c/Makefile (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_3d_c2c/cufft_utils.h (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/Makefile (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/callback_params.h (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/common.cpp (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/common.h (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu (100%) create mode 100644 src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin create mode 100644 src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device_fatbin.h rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_nvrtc_callback_example.cpp (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.cu (100%) rename src/cuda/{H100 => HPC}/cufft/cufft_lto_r2c_c2r/r2c_c2r_reference.h (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_Xgetrf/Makefile (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_Xgetrf/cusolver_utils.h (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_ormqr/Makefile (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu (100%) rename src/cuda/{H100 => HPC}/cusolver/cusolver_ormqr/cusolver_utils.h (100%) rename src/cuda/{H100 => HPC}/external/cugraph (100%) rename src/cuda/{H100 => HPC}/external/newton (100%) create mode 100755 src/cuda/HPC/find_cugraph_tag.sh rename src/cuda/{H100 => HPC}/generate_graph.py (100%) rename src/cuda/{H100 => HPC}/generate_large_ppm.py (98%) rename src/cuda/{H100 => HPC}/generate_large_signal.py (99%) rename src/cuda/{H100 => HPC}/get_dwt_data.sh (95%) rename src/cuda/{H100 => HPC}/get_graph_data.sh (95%) rename src/cuda/{H100 => HPC}/get_image_data.sh (97%) rename src/cuda/{H100 => HPC}/get_vpi_data.sh (94%) rename src/cuda/{H100 => HPC}/graph/bfs_standalone/CMakeLists.txt (100%) rename src/cuda/{H100 => HPC}/graph/bfs_standalone/bfs_standalone.cu (100%) rename src/cuda/{H100 => HPC}/graph/mst_standalone/CMakeLists.txt (100%) rename src/cuda/{H100 => HPC}/graph/mst_standalone/mst_standalone.cu (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3d.cpp (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3d.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3dGPU.cu (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3dGPU.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3dGPUKernel.cuh (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3dReference.cpp (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/FDTD3dReference.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/Makefile (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/exception.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/helper_cuda.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/helper_functions.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/helper_image.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/helper_string.h (100%) rename src/cuda/{H100 => HPC}/image/FDTD3d/helper_timer.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/Makefile (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/dwtHaar1D.cu (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/dwtHaar1D_kernel.cuh (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/exception.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/helper_cuda.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/helper_functions.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/helper_image.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/helper_string.h (100%) rename src/cuda/{H100 => HPC}/image/dwtHaar1D/helper_timer.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/Makefile (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/exception.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_cuda.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_functions.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_gl.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_image.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_math.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_string.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/helper_timer.h (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/recursiveGaussian.cpp (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/recursiveGaussian_cuda.cu (100%) rename src/cuda/{H100 => HPC}/image/recursiveGaussian/recursiveGaussian_kernel.cuh (100%) rename src/cuda/{H100 => HPC}/newton/diffsim_ball/example_diffsim_ball.py (100%) rename src/cuda/{H100 => HPC}/newton/robot_cartpole/example_robot_cartpole.py (100%) rename src/cuda/{H100 => HPC}/newton/setup_newton.sh (100%) rename src/cuda/{H100 => HPC}/setup_vpi.sh (100%) diff --git a/.gitignore b/.gitignore index b729af5d6..2e94221d4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ tmp/ src/cuda/GPU_Microbenchmark/ubench/**/* !src/cuda/GPU_Microbenchmark/ubench/**/*/ !src/cuda/GPU_Microbenchmark/ubench/**/*.* -!src/cuda/GPU_Microbenchmark/ubench/**/Makefile \ No newline at end of file +!src/cuda/GPU_Microbenchmark/ubench/**/Makefile + +# Ignore VPI symlinks +src/cuda/HPC/vpi/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 7120e2048..e48c7a85a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,9 +11,9 @@ path = src/cuda/pytorch_examples url = https://github.com/accel-sim/pytorch_examples.git branch = inference_accelsim_v2 -[submodule "src/cuda/H100/external/cugraph"] - path = src/cuda/H100/external/cugraph +[submodule "src/cuda/HPC/external/cugraph"] + path = src/cuda/HPC/external/cugraph url = https://github.com/rapidsai/cugraph.git -[submodule "src/cuda/H100/external/newton"] - path = src/cuda/H100/external/newton +[submodule "src/cuda/HPC/external/newton"] + path = src/cuda/HPC/external/newton url = https://github.com/newton-physics/newton.git diff --git a/get_data.sh b/get_data.sh index 678a76a21..d2315eb9a 100755 --- a/get_data.sh +++ b/get_data.sh @@ -11,17 +11,16 @@ DATA_ROOT=$BASH_ROOT$DATA_SUBDIR # rm all.gpgpu-sim-app-data.tgz # fi -# Generate H100 benchmark data -echo "Generating H100 benchmark data..." -if [ -f $BASH_ROOT/src/cuda/H100/get_graph_data.sh ]; then - bash $BASH_ROOT/src/cuda/H100/get_graph_data.sh || echo "Warning: Graph data generation failed" +echo "Generating HPC benchmark data..." +if [ -f $BASH_ROOT/src/cuda/HPC/get_graph_data.sh ]; then + bash $BASH_ROOT/src/cuda/HPC/get_graph_data.sh || echo "Warning: Graph data generation failed" fi -if [ -f $BASH_ROOT/src/cuda/H100/get_image_data.sh ]; then - bash $BASH_ROOT/src/cuda/H100/get_image_data.sh || echo "Warning: Image data generation failed" +if [ -f $BASH_ROOT/src/cuda/HPC/get_image_data.sh ]; then + bash $BASH_ROOT/src/cuda/HPC/get_image_data.sh || echo "Warning: Image data generation failed" fi -if [ -f $BASH_ROOT/src/cuda/H100/get_dwt_data.sh ]; then - bash $BASH_ROOT/src/cuda/H100/get_dwt_data.sh || echo "Warning: DWT data generation failed" +if [ -f $BASH_ROOT/src/cuda/HPC/get_dwt_data.sh ]; then + bash $BASH_ROOT/src/cuda/HPC/get_dwt_data.sh || echo "Warning: DWT data generation failed" fi -if [ -f $BASH_ROOT/src/cuda/H100/get_vpi_data.sh ]; then - bash $BASH_ROOT/src/cuda/H100/get_vpi_data.sh +if [ -f $BASH_ROOT/src/cuda/HPC/get_vpi_data.sh ]; then + bash $BASH_ROOT/src/cuda/HPC/get_vpi_data.sh fi diff --git a/src/Makefile b/src/Makefile index 1da014f11..b2f3f1cc8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,7 +4,7 @@ $(error You must run "source setup_environment before calling make") endif ifeq ($(CUDA_GT_7), 1) -all: GPU_Microbenchmark microbench rodinia_2.0-ft cutlass rodinia-3.1 pannotia proxy-apps ispass-2009 lonestargpu-2.0 polybench custom_apps heterosync cuda_samples mlperf_inference vllm huggingface H100 # mlperf_training +all: GPU_Microbenchmark microbench rodinia_2.0-ft cutlass rodinia-3.1 pannotia proxy-apps ispass-2009 lonestargpu-2.0 polybench custom_apps heterosync cuda_samples mlperf_inference vllm huggingface hpc # mlperf_training else ifeq ($(CUDA_GT_4), 1) all: pannotia rodinia_2.0-ft proxy-apps dragon-naive microbench rodinia-3.1 ispass-2009 dragon-cdp lonestargpu-2.0 polybench parboil shoc custom_apps @@ -19,7 +19,7 @@ accelwattch_hw_power: rodinia-3.1_hw_power parboil_hw_power cuda_samples-11.0_hw #Disable clean for now, It has a bug! # clean_dragon-naive clean_pannotia clean_proxy-apps -clean: clean_mlperf_inference clean_rodinia_2.0-ft clean_dragon-cdp clean_ispass-2009 clean_lonestargpu-2.0 clean_custom_apps clean_parboil clean_cutlass clean_rodinia-3.1 clean_heterosync clean_UVMSmart_test clean_cuda_samples clean_huggingface clean_GPU_Microbenchmark clean_H100 +clean: clean_mlperf_inference clean_rodinia_2.0-ft clean_dragon-cdp clean_ispass-2009 clean_lonestargpu-2.0 clean_custom_apps clean_parboil clean_cutlass clean_rodinia-3.1 clean_heterosync clean_UVMSmart_test clean_cuda_samples clean_huggingface clean_GPU_Microbenchmark clean_hpc clean_accelwattch: clean_rodinia-3.1 clean_parboil clean_cutlass clean_cuda_samples-11.0 clean_cuda_samples_hw_power clean_rodinia-3.1_hw_power clean_parboil_hw_power clean_accelwattch_ubench clean_data: @@ -544,13 +544,13 @@ clean_cutlass: rm -rf cuda/cutlass-bench/build ############################################################################### -# H100 Benchmarks - Modern GPU workloads (CUDA 11+, sm_75+) +# Modern HPC Benchmarks - (CUDA 11+, sm_75+) ############################################################################### -H100: +hpc: mkdir -p $(BINDIR)/$(BINSUBDIR)/ # Initialize and update submodules (like cutlass pattern) # Clean up any corrupted submodule directories (check if .git exists) - @for dir in cuda/cuda-samples cuda/H100/external/cugraph cuda/H100/external/newton; do \ + @for dir in cuda/cuda-samples cuda/HPC/external/cugraph cuda/HPC/external/newton; do \ if [ -d $$dir ] && [ ! -d $$dir/.git ]; then \ echo "Removing corrupted submodule directory: $$dir"; \ rm -rf $$dir; \ @@ -558,30 +558,30 @@ H100: done git submodule sync && git submodule update --init --recursive # Build simple apps (cuFFT, cuSolver, image processing) - $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 simple + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple # Build graph apps (cuGraph submodule) - # -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 graph && \ - # cp cuda/H100/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + # -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ + # cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ # Build Newton apps (Newton submodule) - cp -r cuda/H100/newton $(BINDIR)/$(BINSUBDIR)/ + cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ mkdir -p $(BINDIR)/$(BINSUBDIR)/external - cp -r cuda/H100/external/newton $(BINDIR)/$(BINSUBDIR)/external/ + cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole - # Build VPI apps (required) - $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/H100 vpi - cp cuda/H100/vpi/vpi_background_subtractor/build/vpi_background_subtractor $(BINDIR)/$(BINSUBDIR)/ - cp cuda/H100/vpi/vpi_orb_feature_detector/build/vpi_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/ - cp cuda/H100/vpi/vpi_stereo_disparity/build/vpi_stereo_disparity $(BINDIR)/$(BINSUBDIR)/ - -clean_H100: - $(SETENV) $(MAKE) -C cuda/H100 clean - rm -rf cuda/H100/external/cugraph/build - rm -rf cuda/H100/external/newton/build - rm -rf cuda/H100/newton/newton_venv + # Build VPI apps from symlinked sources and copy binaries + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi + cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor + cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector + cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity + +clean_HPC: + $(SETENV) $(MAKE) -C cuda/HPC clean + rm -rf cuda/HPC/external/cugraph/build + rm -rf cuda/HPC/external/newton/build + rm -rf cuda/HPC/newton/newton_venv # clean_deeplearning: # $(SETENV) $(MAKE) $(MAKE_ARGS) noinline=$(noinline) -C cuda/cudnn/mnist clean diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt b/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt deleted file mode 100644 index bb6a1f11a..000000000 --- a/src/cuda/H100/vpi/vpi_background_subtractor/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of NVIDIA CORPORATION nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -cmake_minimum_required(VERSION 3.22) - -project(vpi_background_subtractor) - -set(CMAKE_CXX_STANDARD 11) - -find_package(vpi 4.0 REQUIRED) -find_package(OpenCV REQUIRED) - -add_executable(${PROJECT_NAME} main.cpp) -target_link_libraries(${PROJECT_NAME} vpi opencv_core - opencv_imgproc opencv_imgcodecs opencv_videoio) diff --git a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp b/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp deleted file mode 100644 index 2f36df519..000000000 --- a/src/cuda/H100/vpi/vpi_background_subtractor/main.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* -* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* * Neither the name of NVIDIA CORPORATION nor the names of its -* contributors may be used to endorse or promote products derived -* from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define CHECK_STATUS(STMT) \ - do \ - { \ - VPIStatus status = (STMT); \ - if (status != VPI_SUCCESS) \ - { \ - char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ - vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ - std::ostringstream ss; \ - ss << vpiStatusGetName(status) << ": " << buffer; \ - throw std::runtime_error(ss.str()); \ - } \ - } while (0); - -int main(int argc, char *argv[]) -{ - // OpenCV image that will be wrapped by a VPIImage. - // Define it here so that it's destroyed *after* wrapper is destroyed - cv::Mat cvCurFrame; - - // VPI objects that will be used - VPIStream stream = NULL; - VPIImage imgCurFrame = NULL; - VPIImage bgimage = NULL; - VPIImage fgmask = NULL; - VPIPayload payload = NULL; - - int retval = 0; - - try - { - if (argc != 3 && argc != 4) - { - throw std::runtime_error(std::string("Usage: ") + argv[0] + " [max_frames]"); - } - - // Parse input parameters - std::string strBackend = argv[1]; - std::string strInputVideo = argv[2]; - int maxFrames = -1; // -1 means process all frames - if (argc == 4) - { - maxFrames = std::atoi(argv[3]); - } - - VPIBackend backend; - if (strBackend == "cpu") - { - backend = VPI_BACKEND_CPU; - } - else if (strBackend == "cuda") - { - backend = VPI_BACKEND_CUDA; - } - else - { - throw std::runtime_error("Backend '" + strBackend + "' not recognized."); - } - - // Load the input video - cv::VideoCapture invid; - if (!invid.open(strInputVideo)) - { - throw std::runtime_error("Can't open '" + strInputVideo + "'"); - } - - int32_t width = invid.get(cv::CAP_PROP_FRAME_WIDTH); - int32_t height = invid.get(cv::CAP_PROP_FRAME_HEIGHT); - - // Create the stream where processing will happen. We'll use user-provided backend. - CHECK_STATUS(vpiStreamCreate(backend, &stream)); - - // Create background subtractor payload to be executed on the given backend - // OpenCV delivers us BGR8 images, so the algorithm is configured to accept that. - CHECK_STATUS(vpiCreateBackgroundSubtractor(backend, width, height, VPI_IMAGE_FORMAT_BGR8, &payload)); - - // Create foreground image - CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_U8, 0, &fgmask)); - - // Create background image - CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_BGR8, 0, &bgimage)); - - int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G'); - double fps = invid.get(cv::CAP_PROP_FPS); - - cv::VideoWriter outVideo("fgmask_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height), false); - if (!outVideo.isOpened()) - { - throw std::runtime_error("Can't create output video"); - } - - cv::VideoWriter bgimageVideo("bgimage_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height)); - if (!outVideo.isOpened()) - { - throw std::runtime_error("Can't create output video"); - } - - // Fetch a new frame until video ends - int idxFrame = 1; - - while (invid.read(cvCurFrame)) - { - printf("Processing frame %d\n", idxFrame); - - // Check if we've reached max frames limit - if (maxFrames > 0 && idxFrame > maxFrames) - { - printf("Reached max frames limit (%d), stopping.\n", maxFrames); - break; - } - idxFrame++; - // Wrap frame into a VPIImage - if (imgCurFrame == NULL) - { - CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvCurFrame, 0, &imgCurFrame)); - } - else - { - CHECK_STATUS(vpiImageSetWrappedOpenCVMat(imgCurFrame, cvCurFrame)); - } - - VPIBackgroundSubtractorParams params; - CHECK_STATUS(vpiInitBackgroundSubtractorParams(¶ms)); - params.learningRate = 0.01; - - CHECK_STATUS( - vpiSubmitBackgroundSubtractor(stream, backend, payload, imgCurFrame, fgmask, bgimage, ¶ms)); - - // Wait for processing to finish. - CHECK_STATUS(vpiStreamSync(stream)); - - { - // Now add it to the output video stream - VPIImageData imgdata; - CHECK_STATUS(vpiImageLockData(fgmask, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgdata)); - - cv::Mat outFrame; - CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame)); - - outVideo << outFrame; - - CHECK_STATUS(vpiImageUnlock(fgmask)); - } - - { - VPIImageData bgdata; - CHECK_STATUS(vpiImageLockData(bgimage, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &bgdata)); - - cv::Mat outFrame; - CHECK_STATUS(vpiImageDataExportOpenCVMat(bgdata, &outFrame)); - - bgimageVideo << outFrame; - - CHECK_STATUS(vpiImageUnlock(bgimage)); - } - } - } - catch (std::exception &e) - { - std::cerr << e.what() << std::endl; - retval = 1; - } - - // Destroy all resources used - vpiStreamDestroy(stream); - vpiPayloadDestroy(payload); - - vpiImageDestroy(imgCurFrame); - vpiImageDestroy(fgmask); - vpiImageDestroy(bgimage); - - return retval; -} diff --git a/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt b/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt deleted file mode 100644 index f42b80240..000000000 --- a/src/cuda/H100/vpi/vpi_orb_feature_detector/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of NVIDIA CORPORATION nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -cmake_minimum_required(VERSION 3.22) - -project(vpi_orb_feature_detector) - -find_package(vpi 4.0 REQUIRED) -find_package(OpenCV REQUIRED) - -add_executable(${PROJECT_NAME} main.cpp) -target_link_libraries(${PROJECT_NAME} vpi opencv_imgproc opencv_core opencv_imgcodecs opencv_features2d) diff --git a/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp b/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp deleted file mode 100644 index 61c57a107..000000000 --- a/src/cuda/H100/vpi/vpi_orb_feature_detector/main.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/* -* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* * Neither the name of NVIDIA CORPORATION nor the names of its -* contributors may be used to endorse or promote products derived -* from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#define CHECK_STATUS(STMT) \ - do \ - { \ - VPIStatus status = (STMT); \ - if (status != VPI_SUCCESS) \ - { \ - char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ - vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ - std::ostringstream ss; \ - ss << vpiStatusGetName(status) << ": " << buffer; \ - throw std::runtime_error(ss.str()); \ - } \ - } while (0); - -static cv::Mat DrawKeypoints(cv::Mat img, VPIPyramidalKeypointF32 *kpts, VPIBriefDescriptor *descs, int numKeypoints) -{ - cv::Mat out; - img.convertTo(out, CV_8UC1); - cvtColor(out, out, cv::COLOR_GRAY2BGR); - - if (numKeypoints == 0) - { - return out; - } - - std::vector distances(numKeypoints, 0); - float maxDist = 0.f; - - for (int i = 0; i < numKeypoints; i++) - { - for (int j = 0; j < VPI_BRIEF_DESCRIPTOR_ARRAY_LENGTH; j++) - { - distances[i] += std::bitset<8 * sizeof(uint8_t)>(descs[i].data[j] ^ descs[0].data[j]).count(); - } - if (distances[i] > maxDist) - { - maxDist = distances[i]; - } - } - - uint8_t ids[256]; - std::iota(&ids[0], &ids[0] + 256, 0); - cv::Mat idsMat(256, 1, CV_8UC1, ids); - - cv::Mat cmap; - applyColorMap(idsMat, cmap, cv::COLORMAP_JET); - - for (int i = 0; i < numKeypoints; i++) - { - int cmapIdx = static_cast(std::round((distances[i] / maxDist) * 255)); - - float rescale = std::pow(2, kpts[i].octave); - float x = kpts[i].x * rescale; - float y = kpts[i].y * rescale; - - circle(out, cv::Point(x, y), 3, cmap.at(cmapIdx, 0), -1); - } - - return out; -} - -int main(int argc, char *argv[]) -{ - // OpenCV image that will be wrapped by a VPIImage. - // Define it here so that it's destroyed *after* wrapper is destroyed - cv::Mat cvImage; - - // VPI objects that will be used - VPIImage imgInput = NULL; - VPIImage imgGrayScale = NULL; - - VPIPyramid pyrInput = NULL; - VPIArray keypoints = NULL; - VPIArray descriptors = NULL; - VPIPayload orbPayload = NULL; - VPIStream stream = NULL; - - int retval = 0; - - try - { - // ============================= - // Parse command line parameters - - if (argc != 3) - { - throw std::runtime_error(std::string("Usage: ") + argv[0] + " "); - } - - std::string strBackend = argv[1]; - std::string strInputFileName = argv[2]; - - // Now parse the backend - VPIBackend backend; - - if (strBackend == "cpu") - { - backend = VPI_BACKEND_CPU; - } - else if (strBackend == "cuda") - { - backend = VPI_BACKEND_CUDA; - } - else if (strBackend == "pva") - { - backend = VPI_BACKEND_PVA; - } - else - { - throw std::runtime_error("Backend '" + strBackend + - "' not recognized, it must be either cpu, cuda or pva."); - } - - // Use the selected backend with CPU to be able to read data back from CUDA to CPU for example. - const VPIBackend backendWithCPU = static_cast(backend | VPI_BACKEND_CPU); - - // ===================== - // Load the input image - - cvImage = cv::imread(strInputFileName); - if (cvImage.empty()) - { - throw std::runtime_error("Can't open first image: '" + strInputFileName + "'"); - } - - // ================================= - // Allocate all VPI resources needed - - // Create the stream where processing will happen - CHECK_STATUS(vpiStreamCreate(0, &stream)); - - // Define the algorithm parameters. - VPIORBParams orbParams; - CHECK_STATUS(vpiInitORBParams(&orbParams)); - - orbParams.fastParams.intensityThreshold = 142; - orbParams.maxFeaturesPerLevel = 88; - orbParams.maxPyramidLevels = 3; - - // PVA backend doesn't support default Harris Corner Scoring - if (backend == VPI_BACKEND_PVA) - { - orbParams.scoreType = VPI_CORNER_SCORE_FAST; - } - - // We now wrap the loaded image into a VPIImage object to be used by VPI. - // VPI won't make a copy of it, so the original image must be in scope at all times. - CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImage, 0, &imgInput)); - CHECK_STATUS(vpiImageCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, 0, &imgGrayScale)); - - // For the output arrays capacity we can use the maximum number of features per level multiplied by the - // maximum number of pyramid levels, this will be the de factor maximum for all levels of the input. - int outCapacity = orbParams.maxFeaturesPerLevel * orbParams.maxPyramidLevels; - - // Create the output keypoint array. - CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_PYRAMIDAL_KEYPOINT_F32, backendWithCPU, &keypoints)); - - // Create the output descriptors array. To output corners only use NULL instead. - CHECK_STATUS(vpiArrayCreate(outCapacity, VPI_ARRAY_TYPE_BRIEF_DESCRIPTOR, backendWithCPU, &descriptors)); - - // For the internal buffers capacity we can use the maximum number of features per level multiplied by 20. - // This will make FAST find a large number of corners so then ORB can select the top N corners in - // accordance to Harris score of each corner, where N = maximum number of features per level. - int bufCapacity = orbParams.maxFeaturesPerLevel * 20; - - // Create the payload for ORB Feature Detector algorithm - CHECK_STATUS(vpiCreateORBFeatureDetector(backend, bufCapacity, &orbPayload)); - - // ================ - // Processing stage - - // First convert input to grayscale - VPIBackend convertBackend = (backend == VPI_BACKEND_PVA) ? VPI_BACKEND_CPU : backend; - CHECK_STATUS(vpiSubmitConvertImageFormat(stream, convertBackend, imgInput, imgGrayScale, NULL)); - - // Then, create the Gaussian Pyramid for the image and wait for the execution to finish - CHECK_STATUS(vpiPyramidCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_U8, orbParams.maxPyramidLevels, 0.5, - backend, &pyrInput)); - CHECK_STATUS(vpiSubmitGaussianPyramidGenerator(stream, backend, imgGrayScale, pyrInput, VPI_BORDER_CLAMP)); - - // Then get ORB features and wait for the execution to finish - CHECK_STATUS(vpiSubmitORBFeatureDetector(stream, backend, orbPayload, pyrInput, keypoints, descriptors, - &orbParams, VPI_BORDER_CLAMP)); - - CHECK_STATUS(vpiStreamSync(stream)); - - // ======================================= - // Output processing and saving it to disk - - // Lock output keypoints and scores to retrieve its data on cpu memory - VPIArrayData outKeypointsData; - VPIArrayData outDescriptorsData; - VPIImageData imgData; - CHECK_STATUS(vpiArrayLockData(keypoints, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outKeypointsData)); - CHECK_STATUS(vpiArrayLockData(descriptors, VPI_LOCK_READ, VPI_ARRAY_BUFFER_HOST_AOS, &outDescriptorsData)); - CHECK_STATUS(vpiImageLockData(imgGrayScale, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgData)); - - VPIPyramidalKeypointF32 *outKeypoints = (VPIPyramidalKeypointF32 *)outKeypointsData.buffer.aos.data; - VPIBriefDescriptor *outDescriptors = (VPIBriefDescriptor *)outDescriptorsData.buffer.aos.data; - - cv::Mat img; - CHECK_STATUS(vpiImageDataExportOpenCVMat(imgData, &img)); - - // Draw the keypoints in the output image - cv::Mat outImage = DrawKeypoints(img, outKeypoints, outDescriptors, *outKeypointsData.buffer.aos.sizePointer); - - // Save the output image to disk - imwrite("orb_feature_detector_" + strBackend + ".png", outImage); - - // Done handling outputs, don't forget to unlock them. - CHECK_STATUS(vpiImageUnlock(imgGrayScale)); - CHECK_STATUS(vpiArrayUnlock(keypoints)); - CHECK_STATUS(vpiArrayUnlock(descriptors)); - } - catch (std::exception &e) - { - std::cerr << e.what() << std::endl; - retval = 1; - } - - // ======== - // Clean up - - // Make sure stream is synchronized before destroying the objects - // that might still be in use. - vpiStreamSync(stream); - - vpiImageDestroy(imgInput); - vpiImageDestroy(imgGrayScale); - vpiArrayDestroy(keypoints); - vpiArrayDestroy(descriptors); - vpiPayloadDestroy(orbPayload); - vpiStreamDestroy(stream); - - return retval; -} diff --git a/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt b/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt deleted file mode 100644 index 3597108ad..000000000 --- a/src/cuda/H100/vpi/vpi_stereo_disparity/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of NVIDIA CORPORATION nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -cmake_minimum_required(VERSION 3.22) - -project(vpi_stereo_disparity) - -find_package(vpi 4.0 REQUIRED) -find_package(OpenCV REQUIRED) - -add_executable(${PROJECT_NAME} main.cpp) -target_link_libraries(${PROJECT_NAME} vpi opencv_imgproc opencv_core opencv_imgcodecs) diff --git a/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp b/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp deleted file mode 100644 index c14e35a5c..000000000 --- a/src/cuda/H100/vpi/vpi_stereo_disparity/main.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/* -* Copyright (c) 2019-2024, NVIDIA CORPORATION. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* * Neither the name of NVIDIA CORPORATION nor the names of its -* contributors may be used to endorse or promote products derived -* from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#if CV_MAJOR_VERSION >= 3 -# include -#else -# include // for colormap -# include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define CHECK_STATUS(STMT) \ - do \ - { \ - VPIStatus status = (STMT); \ - if (status != VPI_SUCCESS) \ - { \ - char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ - vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ - std::ostringstream ss; \ - ss << "line " << __LINE__ << " " << vpiStatusGetName(status) << ": " << buffer; \ - throw std::runtime_error(ss.str()); \ - } \ - } while (0); - -int main(int argc, char *argv[]) -{ - // OpenCV image that will be wrapped by a VPIImage. - // Define it here so that it's destroyed *after* wrapper is destroyed - cv::Mat cvImageLeft, cvImageRight; - - // VPI objects that will be used - VPIImage inLeft = NULL; - VPIImage inRight = NULL; - VPIImage tmpLeft = NULL; - VPIImage tmpRight = NULL; - VPIImage stereoLeft = NULL; - VPIImage stereoRight = NULL; - VPIImage disparity = NULL; - VPIImage confidenceMap = NULL; - VPIStream stream = NULL; - VPIPayload stereo = NULL; - - int retval = 0; - - try - { - // ============================= - // Parse command line parameters - - if (argc != 4) - { - throw std::runtime_error(std::string("Usage: ") + argv[0] + - " "); - } - - std::string strBackend = argv[1]; - std::string strLeftFileName = argv[2]; - std::string strRightFileName = argv[3]; - - uint64_t backends; - - if (strBackend == "cuda") - { - backends = VPI_BACKEND_CUDA; - } - else if (strBackend == "ofa") - { - backends = VPI_BACKEND_OFA; - } - else if (strBackend == "ofa-pva-vic") - { - backends = VPI_BACKEND_OFA | VPI_BACKEND_PVA | VPI_BACKEND_VIC; - } - else - { - throw std::runtime_error("Backend '" + strBackend + - "' not recognized, it must be either cuda, ofa or ofa-pva-vic."); - } - - // ===================== - // Load the input images - cvImageLeft = cv::imread(strLeftFileName); - if (cvImageLeft.empty()) - { - throw std::runtime_error("Can't open '" + strLeftFileName + "'"); - } - - cvImageRight = cv::imread(strRightFileName); - if (cvImageRight.empty()) - { - throw std::runtime_error("Can't open '" + strRightFileName + "'"); - } - - // ================================= - // Allocate all VPI resources needed - - int32_t inputWidth = cvImageLeft.cols; - int32_t inputHeight = cvImageLeft.rows; - - // Create the stream that will be used for processing. - CHECK_STATUS(vpiStreamCreate(0, &stream)); - - // We now wrap the loaded images into a VPIImage object to be used by VPI. - // VPI won't make a copy of it, so the original image must be in scope at all times. - CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImageLeft, 0, &inLeft)); - CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImageRight, 0, &inRight)); - - // Format conversion parameters needed for input pre-processing - VPIConvertImageFormatParams convParams; - CHECK_STATUS(vpiInitConvertImageFormatParams(&convParams)); - - // Initialize default parameters - VPIStereoDisparityEstimatorCreationParams createParams; - CHECK_STATUS(vpiInitStereoDisparityEstimatorCreationParams(&createParams)); - - // Select max disparity that works well for the chair_stereo_{left,right}_1920.png files - createParams.maxDisparity = 256; - - // Default format and size for input stereo pair (some backends require adjustments, see below) - VPIImageFormat stereoFormat = VPI_IMAGE_FORMAT_Y8_ER; - - int stereoWidth = inputWidth; - int stereoHeight = inputHeight; - - // Default format and size for output - VPIImageFormat disparityFormat = VPI_IMAGE_FORMAT_S16; - - int outputWidth = inputWidth; - int outputHeight = inputHeight; - - // Override some backend-dependent parameters - if (strBackend.find("ofa") != std::string::npos) - { - // Implementations using OFA require BL input - stereoFormat = VPI_IMAGE_FORMAT_Y8_ER_BL; - - if (strBackend == "ofa") - { - // when using OFA alone, output must also be BL - disparityFormat = VPI_IMAGE_FORMAT_S16_BL; - } - - // Using downscale factor with OFA improves performance - createParams.downscaleFactor = 2; - outputWidth = (inputWidth + createParams.downscaleFactor - 1) / createParams.downscaleFactor; - outputHeight = (inputHeight + createParams.downscaleFactor - 1) / createParams.downscaleFactor; - - // Output width including downscaleFactor must be at least max(64, maxDisparity/downscaleFactor) when the - // OFA+PVA+VIC backend is used - if (strBackend.find("pva") != std::string::npos) - { - int minWidth = std::max(createParams.maxDisparity / createParams.downscaleFactor, outputWidth); - outputWidth = std::max(64, minWidth); - outputHeight = (inputHeight * outputWidth) / inputWidth; - stereoWidth = outputWidth * createParams.downscaleFactor; - stereoHeight = outputHeight * createParams.downscaleFactor; - } - } - - // Create the payload for Stereo Disparity algorithm. - // Payload is created before the image objects so that non-supported backends can be trapped with an error. - CHECK_STATUS(vpiCreateStereoDisparityEstimator(backends, stereoWidth, stereoHeight, stereoFormat, &createParams, - &stereo)); - - // Create the output image where the disparity map will be stored. - CHECK_STATUS(vpiImageCreate(outputWidth, outputHeight, disparityFormat, 0, &disparity)); - - // Create the input stereo images - CHECK_STATUS(vpiImageCreate(stereoWidth, stereoHeight, stereoFormat, 0, &stereoLeft)); - CHECK_STATUS(vpiImageCreate(stereoWidth, stereoHeight, stereoFormat, 0, &stereoRight)); - - // Create the confidence image if the backend can support it - if (strBackend == "ofa-pva-vic" || strBackend == "cuda") - { - CHECK_STATUS(vpiImageCreate(outputWidth, outputHeight, VPI_IMAGE_FORMAT_U16, 0, &confidenceMap)); - } - - // If a rescale of the input is required, create temporary images for the initial format conversion. - bool const isRescaleRequired = (stereoWidth != inputWidth) || (stereoHeight != inputHeight); - if (isRescaleRequired) - { - CHECK_STATUS(vpiImageCreate(inputWidth, inputHeight, stereoFormat, 0, &tmpLeft)); - CHECK_STATUS(vpiImageCreate(inputWidth, inputHeight, stereoFormat, 0, &tmpRight)); - } - - // ================ - // Processing stage - - // Start with default parameters, and override some values depending on what backend is used. - VPIStereoDisparityEstimatorParams submitParams; - CHECK_STATUS(vpiInitStereoDisparityEstimatorParams(&submitParams)); - if (strBackend == "ofa-pva-vic") - { - // The INFERENCE confidence type achieves better performance with OFA+PVA+VIC backend. The only tradeoff is - // that the deep-learning based confidence map is not easily expressed as a function of left and right - // disparity estimates, in contrast to ABSOLUTE or RELATIVE confidence type. - submitParams.confidenceType = VPI_STEREO_CONFIDENCE_INFERENCE; - } - else if (strBackend == "cuda") - { - // The chair_stereo_{left,right}_1920.png inputs benefit from a higher confidence threshold with CUDA - submitParams.confidenceThreshold = UINT16_MAX - 10000; - } - - // ----------------- - // Pre-process input - if (isRescaleRequired) - { - // We require a conversion with CUDA only because we loaded the images in the default BGR format from OpenCV - // and the VIC backend does not support 3-channel RGB/BGR image formats. - // Alternatively, we could load grayscale images and handle the conversion+rescale in one operation on VIC. - - // Convert opencv input to grayscale format using CUDA - CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inLeft, tmpLeft, &convParams)); - CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inRight, tmpRight, &convParams)); - - // Rescale on VIC - CHECK_STATUS( - vpiSubmitRescale(stream, VPI_BACKEND_VIC, tmpLeft, stereoLeft, VPI_INTERP_LINEAR, VPI_BORDER_CLAMP, 0)); - CHECK_STATUS(vpiSubmitRescale(stream, VPI_BACKEND_VIC, tmpRight, stereoRight, VPI_INTERP_LINEAR, - VPI_BORDER_CLAMP, 0)); - } - else - { - // Convert opencv input to grayscale format using CUDA - CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inLeft, stereoLeft, &convParams)); - CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, inRight, stereoRight, &convParams)); - } - - // ------------------------------ - // Do stereo disparity estimation - - // Submit it with the input and output images - CHECK_STATUS(vpiSubmitStereoDisparityEstimator(stream, backends, stereo, stereoLeft, stereoRight, disparity, - confidenceMap, &submitParams)); - - // Wait until the algorithm finishes processing - CHECK_STATUS(vpiStreamSync(stream)); - - // ======================================== - // Output pre-processing and saving to disk - // Lock output to retrieve its data on cpu memory - VPIImageData data; - CHECK_STATUS(vpiImageLockData(disparity, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &data)); - - // Make an OpenCV matrix out of this image - cv::Mat cvDisparity; - CHECK_STATUS(vpiImageDataExportOpenCVMat(data, &cvDisparity)); - - // Scale result and write it to disk. Disparities are in Q10.5 format, - // so to map it to float, it gets divided by 32. Then the resulting disparity range, - // from 0 to maxDisparity gets mapped to 0-255 for proper output. - cvDisparity.convertTo(cvDisparity, CV_8UC1, 255.0 / (32 * createParams.maxDisparity), 0); - - // Apply JET colormap to turn the disparities into color. - // Reddish hues represent objects closer to the camera, blueish are farther away. - cv::Mat cvDisparityColor; - applyColorMap(cvDisparity, cvDisparityColor, cv::COLORMAP_JET); - - // Done handling output, don't forget to unlock it. - CHECK_STATUS(vpiImageUnlock(disparity)); - - // If we have a confidence map, adjust it for display and write it to disk too. - if (confidenceMap) - { - // Lock the image data and export to cv::Mat - VPIImageData data; - CHECK_STATUS(vpiImageLockData(confidenceMap, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &data)); - cv::Mat cvConfidence; - CHECK_STATUS(vpiImageDataExportOpenCVMat(data, &cvConfidence)); - - // Confidence map varies from 0 to 65535, we scale it to [0-255]. - cvConfidence.convertTo(cvConfidence, CV_8UC1, 255.0 / 65535, 0); - imwrite("confidence_" + strBackend + ".png", cvConfidence); - - CHECK_STATUS(vpiImageUnlock(confidenceMap)); - - // When pixel confidence is 0, we would like its color in the disparity image to be black. - cv::Mat cvMask; - threshold(cvConfidence, cvMask, 1, 255, cv::THRESH_BINARY); - cvtColor(cvMask, cvMask, cv::COLOR_GRAY2BGR); - bitwise_and(cvDisparityColor, cvMask, cvDisparityColor); - } - - imwrite("disparity_" + strBackend + ".png", cvDisparityColor); - } - catch (std::exception &e) - { - std::cerr << e.what() << std::endl; - retval = 1; - } - - // ======== - // Clean up - - // Destroying stream first makes sure that all work submitted to - // it is finished. - vpiStreamDestroy(stream); - - // Only then we can destroy the other objects, as we're sure they - // aren't being used anymore. - - vpiImageDestroy(inLeft); - vpiImageDestroy(inRight); - vpiImageDestroy(tmpLeft); - vpiImageDestroy(tmpRight); - vpiImageDestroy(stereoLeft); - vpiImageDestroy(stereoRight); - vpiImageDestroy(confidenceMap); - vpiImageDestroy(disparity); - vpiPayloadDestroy(stereo); - - return retval; -} diff --git a/src/cuda/H100/Makefile b/src/cuda/HPC/Makefile similarity index 71% rename from src/cuda/H100/Makefile rename to src/cuda/HPC/Makefile index 066ae9941..8ad3e9500 100644 --- a/src/cuda/H100/Makefile +++ b/src/cuda/HPC/Makefile @@ -47,6 +47,7 @@ newton: @echo "Newton apps ready (Python-based, no build needed)" # VPI apps (VPI library from system - required) +# Symlink sources from /opt/nvidia/vpi*/samples/ and build locally vpi: @echo "Searching for VPI installation..." @VPI_ROOT=$$(find /opt/nvidia -maxdepth 1 -name "vpi*" -type d 2>/dev/null | head -1); \ @@ -56,18 +57,27 @@ vpi: exit 1; \ fi; \ echo "Found VPI at $$VPI_ROOT"; \ - export VPI_ROOT=$$VPI_ROOT; \ + VPI_SAMPLES=$$VPI_ROOT/samples; \ + echo "Creating symlinks to VPI sample sources..."; \ + mkdir -p vpi/vpi_background_subtractor vpi/vpi_orb_feature_detector vpi/vpi_stereo_disparity; \ + ln -sf $$VPI_SAMPLES/14-background_subtractor/main.cpp vpi/vpi_background_subtractor/main.cpp; \ + ln -sf $$VPI_SAMPLES/14-background_subtractor/CMakeLists.txt vpi/vpi_background_subtractor/CMakeLists.txt; \ + ln -sf $$VPI_SAMPLES/18-orb_feature_detector/main.cpp vpi/vpi_orb_feature_detector/main.cpp; \ + ln -sf $$VPI_SAMPLES/18-orb_feature_detector/CMakeLists.txt vpi/vpi_orb_feature_detector/CMakeLists.txt; \ + ln -sf $$VPI_SAMPLES/02-stereo_disparity/main.cpp vpi/vpi_stereo_disparity/main.cpp; \ + ln -sf $$VPI_SAMPLES/02-stereo_disparity/CMakeLists.txt vpi/vpi_stereo_disparity/CMakeLists.txt; \ + echo "Building VPI apps from symlinked sources..."; \ mkdir -p vpi/vpi_background_subtractor/build && \ cd vpi/vpi_background_subtractor/build && \ - cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) && \ + cmake .. && $(MAKE) && \ cd ../../.. && \ mkdir -p vpi/vpi_orb_feature_detector/build && \ cd vpi/vpi_orb_feature_detector/build && \ - cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) && \ + cmake .. && $(MAKE) && \ cd ../../.. && \ mkdir -p vpi/vpi_stereo_disparity/build && \ cd vpi/vpi_stereo_disparity/build && \ - cmake .. -DVPI_ROOT=$$VPI_ROOT && $(MAKE) + cmake .. && $(MAKE) clean: # Clean bin directory @@ -86,5 +96,6 @@ clean: -rm -rf external/cugraph/cpp/build # Clean Newton -rm -rf newton/newton_venv - # Clean VPI apps + # Clean VPI apps and symlinks -rm -rf vpi/*/build + -rm -f vpi/*/main.cpp vpi/*/CMakeLists.txt diff --git a/src/cuda/H100/README.md b/src/cuda/HPC/README.md similarity index 100% rename from src/cuda/H100/README.md rename to src/cuda/HPC/README.md diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/Makefile b/src/cuda/HPC/cufft/cufft_3d_c2c/Makefile similarity index 100% rename from src/cuda/H100/cufft/cufft_3d_c2c/Makefile rename to src/cuda/HPC/cufft/cufft_3d_c2c/Makefile diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu b/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu similarity index 100% rename from src/cuda/H100/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu rename to src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu diff --git a/src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h b/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_utils.h similarity index 100% rename from src/cuda/H100/cufft/cufft_3d_c2c/cufft_utils.h rename to src/cuda/HPC/cufft/cufft_3d_c2c/cufft_utils.h diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/Makefile similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/Makefile rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/Makefile diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/callback_params.h b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/callback_params.h similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/callback_params.h rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/callback_params.h diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.cpp b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/common.cpp similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.cpp rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/common.cpp diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/common.h similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/common.h rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/common.h diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/nvrtc_helper.h diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_legacy_callback_example.cu diff --git a/src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu similarity index 100% rename from src/cuda/H100/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu rename to src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.cu diff --git a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin new file mode 100644 index 0000000000000000000000000000000000000000..24929dd3fc24cf1b0f2c07d055f7f6e83ea31061 GIT binary patch literal 2536 zcmV<{900008009Ab0001B2><{9000192><{<0000$0096? z00000000000000HAOQdX0002V`9Mqn0000S3jhEB0000;0000e0000jW^{TzFd!{( za%E#*WNCIiF(55)a%E#*b8&KXJux6HW^G|)Jux5v000000002u*yW`LsjO&I-WGiZ z*EbC}Z}*r!g{9nJw|~04>RDN`Q%q$Vs`8(rU(8bev=rrH*TgJkhcft}{x9sjbm@~9AgdPaK@XtXkkfeNUT-hjz#`yi3k?-`9sSIXsQ6szG zQ=6--M>G=N2s5cunLun%zZS`K;tQ4HGcX~4BK5BXn%z%*WVbGFwYK4N`3sTjx1ZSv3Vw5O4YR}=3mH;S@Awn4#Y2^5^4L=FMlbtu7 z5BWCZNcoiPV{GCxHXs?fXn2$mr_O1tn6FA3!6yIqRz7tDBqOHDgHVxmDq5>Tu}-Dh zY-`ft)9U)jCl}jK`6>DeWnhJV>f>M+-}|Pq+M3;PN?=iQ5%xT6b1YhoJlRSbbJSCF zfq^qUO*4JwaW&~6by@3fKiyJs)wcQ^A)wimdMRKBd=vr9h^CzS^Elod-2og6YW#Arj7pX zN}&(e^+Yj+eGo|yjMPn>X9o$pyd|VYUgf@H1FWo1vrIM$Pdnno6O|`m&*qP@A}%Ud z^5u8tfzel5?C?_O^q!(r{PX@o=Vb%bCRbP7wa_Zw!Opr_h3XyhfUsrz1h{d6uIY}a zMe8#qeVPp)3^dQi99&#spcx1nuZIMpa%SvU8)?`GDj?6PE-`FrC!n&wx2KJ%r%^%7 zG|VMjCH75vSW9yeAcNL1*zNfEuvolqM5BBGx%iar+Y&?TzJ)LMe!~%nPcKG``Wgph zxV3-wr|=O&=p=ffkl zs2<3LAeoHGdCppa;_k7lgxT+hh_;6es+Nv^T>r3&31+SEwUDOL#D zJp`)W(^T(6i>83^? z)wL#xX2>l$@S_i6MiO%tR+B^iD+DfdX{%mMfsBTqn75}ot@69PWBYSCIZo1zQACa<0_78c6B-;oP4rxvcu{fCMBSbD*{!i1JdA;voLN99pMc`4{B9TwxPqsl8niS#HYVUl;1jT}EgnxP#3PWgBP>Kx zn#k8?VOg5xf~&>^eud!BtELg9V4-jg+aV^x+P4z8cBXCe)=WV;$7?!`CYD(e!}9%4 zEm@)tM9{MHIC2~qY#aYm%VLp!;@oyZT#l5(;?ML&K7*Ek@tsHy;T?g4Fh?s$Bht%B zKJL{MO#_zNk$3)Sc_hos&-isT>VS6z9OekNz;atXw516L}%g(~h}-nYwHdX_0BKP%JKVU^^zDmP=aHFfm)HhIjq@}j_C3U(yS zq9~tWw^TobGT0+1k-%Y} z-igF%Z^f(p=gJZhii{777=BrVy`GpcTiAN-87!$V9ainc4xJq%2z| z{8G?IU32q%yIiMAMG!L@%~7zPWLO%1K~??Z6F99~?&$PQUy^Ut=AZ^dO> " + exit 1 +fi + +cd "$CUGRAPH_DIR" || exit 1 + +# Fetch all tags +git fetch --tags --quiet 2>/dev/null + +# Get all tags sorted by version (newest first) +TAGS=$(git tag -l 'v*' | sort -V -r) + +# For each tag, check if it supports the CUDA version +for TAG in $TAGS; do + # Checkout the tag quietly + git checkout "$TAG" --quiet 2>/dev/null || continue + + # Check rapids-cmake or CMakeLists.txt for CUDA version support + # Look for CUDA version specifications in cmake files + if [ -f "rapids-cmake/rapids-cuda/rapids_cuda_init_architectures.cmake" ]; then + CUDA_FILE="rapids-cmake/rapids-cuda/rapids_cuda_init_architectures.cmake" + elif [ -f "cpp/CMakeLists.txt" ]; then + CUDA_FILE="cpp/CMakeLists.txt" + else + continue + fi + + # Extract supported CUDA versions from the file + # Look for patterns like "CUDA 12.8" or "CUDA_VERSION 12.8" + if grep -q "$CUDA_VERSION" "$CUDA_FILE" 2>/dev/null || \ + grep -qE "CUDA.*$CUDA_VERSION|$CUDA_VERSION.*CUDA" "$CUDA_FILE" 2>/dev/null; then + echo "$TAG" + exit 0 + fi +done + +# If no tag found, return the latest tag +LATEST=$(git tag -l 'v*' | sort -V -r | head -1) +echo "$LATEST" diff --git a/src/cuda/H100/generate_graph.py b/src/cuda/HPC/generate_graph.py similarity index 100% rename from src/cuda/H100/generate_graph.py rename to src/cuda/HPC/generate_graph.py diff --git a/src/cuda/H100/generate_large_ppm.py b/src/cuda/HPC/generate_large_ppm.py similarity index 98% rename from src/cuda/H100/generate_large_ppm.py rename to src/cuda/HPC/generate_large_ppm.py index 571007641..ee30ca895 100755 --- a/src/cuda/H100/generate_large_ppm.py +++ b/src/cuda/HPC/generate_large_ppm.py @@ -79,7 +79,7 @@ def generate_ppm(width, height, filename): # Generate standard test sizes import os script_dir = os.path.dirname(os.path.abspath(__file__)) - base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/H100/recursiveGaussian/data") + base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/HPC/recursiveGaussian/data") # Create directory if it doesn't exist os.makedirs(base_dir, exist_ok=True) diff --git a/src/cuda/H100/generate_large_signal.py b/src/cuda/HPC/generate_large_signal.py similarity index 99% rename from src/cuda/H100/generate_large_signal.py rename to src/cuda/HPC/generate_large_signal.py index 4c5644298..53853446b 100755 --- a/src/cuda/H100/generate_large_signal.py +++ b/src/cuda/HPC/generate_large_signal.py @@ -88,7 +88,7 @@ def generate_signal(size_power, filename, epsilon=0.001): # Generate standard test sizes import os script_dir = os.path.dirname(os.path.abspath(__file__)) - base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/H100/dwtHaar1D/data") + base_dir = os.path.join(script_dir, "../../../data_dirs/cuda/HPC/dwtHaar1D/data") # Create directory if it doesn't exist os.makedirs(base_dir, exist_ok=True) diff --git a/src/cuda/H100/get_dwt_data.sh b/src/cuda/HPC/get_dwt_data.sh similarity index 95% rename from src/cuda/H100/get_dwt_data.sh rename to src/cuda/HPC/get_dwt_data.sh index d958081ad..225020292 100755 --- a/src/cuda/H100/get_dwt_data.sh +++ b/src/cuda/HPC/get_dwt_data.sh @@ -2,7 +2,7 @@ # Generate random signal data for DWT SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/dwtHaar1D/data/" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/HPC/dwtHaar1D/data/" mkdir -p "$DATA_DIR" # Link gold files and signal files from cuda-samples diff --git a/src/cuda/H100/get_graph_data.sh b/src/cuda/HPC/get_graph_data.sh similarity index 95% rename from src/cuda/H100/get_graph_data.sh rename to src/cuda/HPC/get_graph_data.sh index 62c4d2397..2df0cff25 100755 --- a/src/cuda/H100/get_graph_data.sh +++ b/src/cuda/HPC/get_graph_data.sh @@ -2,7 +2,7 @@ # Download standard graph datasets and generate synthetic graph SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/H100/graph/data/" +DATA_DIR="$SCRIPT_DIR/../../../data_dirs/cuda/HPC/graph/data/" mkdir -p "$DATA_DIR" # Download karate club (standard benchmark from SuiteSparse Matrix Collection) diff --git a/src/cuda/H100/get_image_data.sh b/src/cuda/HPC/get_image_data.sh similarity index 97% rename from src/cuda/H100/get_image_data.sh rename to src/cuda/HPC/get_image_data.sh index e9fb17cc3..8dbf3ea10 100755 --- a/src/cuda/H100/get_image_data.sh +++ b/src/cuda/HPC/get_image_data.sh @@ -1,7 +1,7 @@ #!/bin/bash # Generate test images for recursiveGaussian -DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/H100/recursiveGaussian/data/" +DATA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../../data_dirs/cuda/HPC/recursiveGaussian/data/" mkdir -p "$DATA_DIR" # Generate test PPM images using Python (portable, no ImageMagick dependency) diff --git a/src/cuda/H100/get_vpi_data.sh b/src/cuda/HPC/get_vpi_data.sh similarity index 94% rename from src/cuda/H100/get_vpi_data.sh rename to src/cuda/HPC/get_vpi_data.sh index 55ec41fdf..c5e7b1a7d 100755 --- a/src/cuda/H100/get_vpi_data.sh +++ b/src/cuda/HPC/get_vpi_data.sh @@ -2,7 +2,7 @@ # Link VPI sample data from VPI installation SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DATA_ROOT="$SCRIPT_DIR/../../../data_dirs/cuda/H100" +DATA_ROOT="$SCRIPT_DIR/../../../data_dirs/cuda/HPC" # Find VPI installation VPI_ROOT=$(find /opt/nvidia -maxdepth 1 -name "vpi*" -type d 2>/dev/null | head -1) diff --git a/src/cuda/H100/graph/bfs_standalone/CMakeLists.txt b/src/cuda/HPC/graph/bfs_standalone/CMakeLists.txt similarity index 100% rename from src/cuda/H100/graph/bfs_standalone/CMakeLists.txt rename to src/cuda/HPC/graph/bfs_standalone/CMakeLists.txt diff --git a/src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu b/src/cuda/HPC/graph/bfs_standalone/bfs_standalone.cu similarity index 100% rename from src/cuda/H100/graph/bfs_standalone/bfs_standalone.cu rename to src/cuda/HPC/graph/bfs_standalone/bfs_standalone.cu diff --git a/src/cuda/H100/graph/mst_standalone/CMakeLists.txt b/src/cuda/HPC/graph/mst_standalone/CMakeLists.txt similarity index 100% rename from src/cuda/H100/graph/mst_standalone/CMakeLists.txt rename to src/cuda/HPC/graph/mst_standalone/CMakeLists.txt diff --git a/src/cuda/H100/graph/mst_standalone/mst_standalone.cu b/src/cuda/HPC/graph/mst_standalone/mst_standalone.cu similarity index 100% rename from src/cuda/H100/graph/mst_standalone/mst_standalone.cu rename to src/cuda/HPC/graph/mst_standalone/mst_standalone.cu diff --git a/src/cuda/H100/image/FDTD3d/FDTD3d.cpp b/src/cuda/HPC/image/FDTD3d/FDTD3d.cpp similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3d.cpp rename to src/cuda/HPC/image/FDTD3d/FDTD3d.cpp diff --git a/src/cuda/H100/image/FDTD3d/FDTD3d.h b/src/cuda/HPC/image/FDTD3d/FDTD3d.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3d.h rename to src/cuda/HPC/image/FDTD3d/FDTD3d.h diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu b/src/cuda/HPC/image/FDTD3d/FDTD3dGPU.cu similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3dGPU.cu rename to src/cuda/HPC/image/FDTD3d/FDTD3dGPU.cu diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPU.h b/src/cuda/HPC/image/FDTD3d/FDTD3dGPU.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3dGPU.h rename to src/cuda/HPC/image/FDTD3d/FDTD3dGPU.h diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh b/src/cuda/HPC/image/FDTD3d/FDTD3dGPUKernel.cuh similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3dGPUKernel.cuh rename to src/cuda/HPC/image/FDTD3d/FDTD3dGPUKernel.cuh diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp b/src/cuda/HPC/image/FDTD3d/FDTD3dReference.cpp similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3dReference.cpp rename to src/cuda/HPC/image/FDTD3d/FDTD3dReference.cpp diff --git a/src/cuda/H100/image/FDTD3d/FDTD3dReference.h b/src/cuda/HPC/image/FDTD3d/FDTD3dReference.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/FDTD3dReference.h rename to src/cuda/HPC/image/FDTD3d/FDTD3dReference.h diff --git a/src/cuda/H100/image/FDTD3d/Makefile b/src/cuda/HPC/image/FDTD3d/Makefile similarity index 100% rename from src/cuda/H100/image/FDTD3d/Makefile rename to src/cuda/HPC/image/FDTD3d/Makefile diff --git a/src/cuda/H100/image/FDTD3d/exception.h b/src/cuda/HPC/image/FDTD3d/exception.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/exception.h rename to src/cuda/HPC/image/FDTD3d/exception.h diff --git a/src/cuda/H100/image/FDTD3d/helper_cuda.h b/src/cuda/HPC/image/FDTD3d/helper_cuda.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/helper_cuda.h rename to src/cuda/HPC/image/FDTD3d/helper_cuda.h diff --git a/src/cuda/H100/image/FDTD3d/helper_functions.h b/src/cuda/HPC/image/FDTD3d/helper_functions.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/helper_functions.h rename to src/cuda/HPC/image/FDTD3d/helper_functions.h diff --git a/src/cuda/H100/image/FDTD3d/helper_image.h b/src/cuda/HPC/image/FDTD3d/helper_image.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/helper_image.h rename to src/cuda/HPC/image/FDTD3d/helper_image.h diff --git a/src/cuda/H100/image/FDTD3d/helper_string.h b/src/cuda/HPC/image/FDTD3d/helper_string.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/helper_string.h rename to src/cuda/HPC/image/FDTD3d/helper_string.h diff --git a/src/cuda/H100/image/FDTD3d/helper_timer.h b/src/cuda/HPC/image/FDTD3d/helper_timer.h similarity index 100% rename from src/cuda/H100/image/FDTD3d/helper_timer.h rename to src/cuda/HPC/image/FDTD3d/helper_timer.h diff --git a/src/cuda/H100/image/dwtHaar1D/Makefile b/src/cuda/HPC/image/dwtHaar1D/Makefile similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/Makefile rename to src/cuda/HPC/image/dwtHaar1D/Makefile diff --git a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu b/src/cuda/HPC/image/dwtHaar1D/dwtHaar1D.cu similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/dwtHaar1D.cu rename to src/cuda/HPC/image/dwtHaar1D/dwtHaar1D.cu diff --git a/src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh b/src/cuda/HPC/image/dwtHaar1D/dwtHaar1D_kernel.cuh similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/dwtHaar1D_kernel.cuh rename to src/cuda/HPC/image/dwtHaar1D/dwtHaar1D_kernel.cuh diff --git a/src/cuda/H100/image/dwtHaar1D/exception.h b/src/cuda/HPC/image/dwtHaar1D/exception.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/exception.h rename to src/cuda/HPC/image/dwtHaar1D/exception.h diff --git a/src/cuda/H100/image/dwtHaar1D/helper_cuda.h b/src/cuda/HPC/image/dwtHaar1D/helper_cuda.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/helper_cuda.h rename to src/cuda/HPC/image/dwtHaar1D/helper_cuda.h diff --git a/src/cuda/H100/image/dwtHaar1D/helper_functions.h b/src/cuda/HPC/image/dwtHaar1D/helper_functions.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/helper_functions.h rename to src/cuda/HPC/image/dwtHaar1D/helper_functions.h diff --git a/src/cuda/H100/image/dwtHaar1D/helper_image.h b/src/cuda/HPC/image/dwtHaar1D/helper_image.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/helper_image.h rename to src/cuda/HPC/image/dwtHaar1D/helper_image.h diff --git a/src/cuda/H100/image/dwtHaar1D/helper_string.h b/src/cuda/HPC/image/dwtHaar1D/helper_string.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/helper_string.h rename to src/cuda/HPC/image/dwtHaar1D/helper_string.h diff --git a/src/cuda/H100/image/dwtHaar1D/helper_timer.h b/src/cuda/HPC/image/dwtHaar1D/helper_timer.h similarity index 100% rename from src/cuda/H100/image/dwtHaar1D/helper_timer.h rename to src/cuda/HPC/image/dwtHaar1D/helper_timer.h diff --git a/src/cuda/H100/image/recursiveGaussian/Makefile b/src/cuda/HPC/image/recursiveGaussian/Makefile similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/Makefile rename to src/cuda/HPC/image/recursiveGaussian/Makefile diff --git a/src/cuda/H100/image/recursiveGaussian/exception.h b/src/cuda/HPC/image/recursiveGaussian/exception.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/exception.h rename to src/cuda/HPC/image/recursiveGaussian/exception.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_cuda.h b/src/cuda/HPC/image/recursiveGaussian/helper_cuda.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_cuda.h rename to src/cuda/HPC/image/recursiveGaussian/helper_cuda.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_functions.h b/src/cuda/HPC/image/recursiveGaussian/helper_functions.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_functions.h rename to src/cuda/HPC/image/recursiveGaussian/helper_functions.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_gl.h b/src/cuda/HPC/image/recursiveGaussian/helper_gl.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_gl.h rename to src/cuda/HPC/image/recursiveGaussian/helper_gl.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_image.h b/src/cuda/HPC/image/recursiveGaussian/helper_image.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_image.h rename to src/cuda/HPC/image/recursiveGaussian/helper_image.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_math.h b/src/cuda/HPC/image/recursiveGaussian/helper_math.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_math.h rename to src/cuda/HPC/image/recursiveGaussian/helper_math.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_string.h b/src/cuda/HPC/image/recursiveGaussian/helper_string.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_string.h rename to src/cuda/HPC/image/recursiveGaussian/helper_string.h diff --git a/src/cuda/H100/image/recursiveGaussian/helper_timer.h b/src/cuda/HPC/image/recursiveGaussian/helper_timer.h similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/helper_timer.h rename to src/cuda/HPC/image/recursiveGaussian/helper_timer.h diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp b/src/cuda/HPC/image/recursiveGaussian/recursiveGaussian.cpp similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/recursiveGaussian.cpp rename to src/cuda/HPC/image/recursiveGaussian/recursiveGaussian.cpp diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu b/src/cuda/HPC/image/recursiveGaussian/recursiveGaussian_cuda.cu similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/recursiveGaussian_cuda.cu rename to src/cuda/HPC/image/recursiveGaussian/recursiveGaussian_cuda.cu diff --git a/src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh b/src/cuda/HPC/image/recursiveGaussian/recursiveGaussian_kernel.cuh similarity index 100% rename from src/cuda/H100/image/recursiveGaussian/recursiveGaussian_kernel.cuh rename to src/cuda/HPC/image/recursiveGaussian/recursiveGaussian_kernel.cuh diff --git a/src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py b/src/cuda/HPC/newton/diffsim_ball/example_diffsim_ball.py similarity index 100% rename from src/cuda/H100/newton/diffsim_ball/example_diffsim_ball.py rename to src/cuda/HPC/newton/diffsim_ball/example_diffsim_ball.py diff --git a/src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py b/src/cuda/HPC/newton/robot_cartpole/example_robot_cartpole.py similarity index 100% rename from src/cuda/H100/newton/robot_cartpole/example_robot_cartpole.py rename to src/cuda/HPC/newton/robot_cartpole/example_robot_cartpole.py diff --git a/src/cuda/H100/newton/setup_newton.sh b/src/cuda/HPC/newton/setup_newton.sh similarity index 100% rename from src/cuda/H100/newton/setup_newton.sh rename to src/cuda/HPC/newton/setup_newton.sh diff --git a/src/cuda/H100/setup_vpi.sh b/src/cuda/HPC/setup_vpi.sh similarity index 100% rename from src/cuda/H100/setup_vpi.sh rename to src/cuda/HPC/setup_vpi.sh From f34bd0ebebd71a7f0c6b85d2a986278ec6b03f06 Mon Sep 17 00:00:00 2001 From: Anunalla Date: Thu, 26 Mar 2026 12:31:40 +0000 Subject: [PATCH 07/11] modify input args --- src/Makefile | 34 +++---- src/cuda/HPC/Makefile | 16 ++-- .../cufft_3d_c2c/cufft_3d_c2c_scalable.cu | 58 +++++++++--- .../cufft_lto_r2c_c2r_scalable.cpp | 90 +++++++++++++++---- .../cusolver_Xgetrf_scalable.cu | 52 +++++++---- .../cusolver_ormqr/cusolver_ormqr_scalable.cu | 52 +++++++---- src/cuda/HPC/image/FDTD3d/FDTD3d.h | 2 +- 7 files changed, 217 insertions(+), 87 deletions(-) diff --git a/src/Makefile b/src/Makefile index b2f3f1cc8..a4bbbbde3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -557,25 +557,29 @@ hpc: fi; \ done git submodule sync && git submodule update --init --recursive + # Build simple apps (cuFFT, cuSolver, image processing) - $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple + # $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple + # Build graph apps (cuGraph submodule) - # -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ - # cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ + cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + # Build Newton apps (Newton submodule) - cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ - mkdir -p $(BINDIR)/$(BINSUBDIR)/external - cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ - bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh - echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball - chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball - echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole - chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + # cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ + # mkdir -p $(BINDIR)/$(BINSUBDIR)/external + # cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ + # bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh + # echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + # chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + # echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + # chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + # Build VPI apps from symlinked sources and copy binaries - $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi - cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor - cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector - cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity + # $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi + # cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor + # cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector + # cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity clean_HPC: $(SETENV) $(MAKE) -C cuda/HPC clean diff --git a/src/cuda/HPC/Makefile b/src/cuda/HPC/Makefile index 8ad3e9500..f8d1b267b 100644 --- a/src/cuda/HPC/Makefile +++ b/src/cuda/HPC/Makefile @@ -2,7 +2,8 @@ .PHONY: all simple graph newton vpi clean cufft cusolver image # Default: Build all apps (submodules initialized by parent Makefile) -all: simple graph newton vpi +# all: simple graph newton vpi +all: graph # Simple apps (cuFFT, cuSolver, image processing - CUDA toolkit only) simple: cufft cusolver image @@ -29,11 +30,11 @@ graph: echo "Building graph apps..." && \ cd ../.. && \ export CUGRAPH_ROOT="$(shell pwd)/external/cugraph" && \ - mkdir -p graph/bfs_standalone/build && \ - cd graph/bfs_standalone/build && \ - cmake .. -DCMAKE_CUDA_COMPILER=$(CUDA_INSTALL_PATH)/bin/nvcc && \ - $(MAKE) && \ - cd ../../.. && \ + # mkdir -p graph/bfs_standalone/build && \ + # cd graph/bfs_standalone/build && \ + # cmake .. -DCMAKE_CUDA_COMPILER=$(CUDA_INSTALL_PATH)/bin/nvcc && \ + # $(MAKE) && \ + # cd ../../.. && \ mkdir -p graph/mst_standalone/build && \ cd graph/mst_standalone/build && \ cmake .. -DCMAKE_CUDA_COMPILER=$(CUDA_INSTALL_PATH)/bin/nvcc && \ @@ -59,8 +60,7 @@ vpi: echo "Found VPI at $$VPI_ROOT"; \ VPI_SAMPLES=$$VPI_ROOT/samples; \ echo "Creating symlinks to VPI sample sources..."; \ - mkdir -p vpi/vpi_background_subtractor vpi/vpi_orb_feature_detector vpi/vpi_stereo_disparity; \ - ln -sf $$VPI_SAMPLES/14-background_subtractor/main.cpp vpi/vpi_background_subtractor/main.cpp; \ + mkdir -p vpi/vpi_orb_feature_detector vpi/vpi_stereo_disparity; \ ln -sf $$VPI_SAMPLES/14-background_subtractor/CMakeLists.txt vpi/vpi_background_subtractor/CMakeLists.txt; \ ln -sf $$VPI_SAMPLES/18-orb_feature_detector/main.cpp vpi/vpi_orb_feature_detector/main.cpp; \ ln -sf $$VPI_SAMPLES/18-orb_feature_detector/CMakeLists.txt vpi/vpi_orb_feature_detector/CMakeLists.txt; \ diff --git a/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu b/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu index 87e2e6023..4263035d6 100644 --- a/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu +++ b/src/cuda/HPC/cufft/cufft_3d_c2c/cufft_3d_c2c_scalable.cu @@ -33,24 +33,60 @@ int main(int argc, char *argv[]) { cufftHandle plan; cudaStream_t stream = NULL; - // Parse size argument (small/medium/large) - int n = 16; // Default: small + // Default values + int n = 16; int batch_size = 4; - const char* size_name = "small"; - if (argc > 1) { - if (strcmp(argv[1], "small") == 0) { + // Parse named command-line arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--n") == 0 || strcmp(argv[i], "-n") == 0) { + if (i + 1 < argc) { + n = atoi(argv[++i]); + } else { + std::printf("Error: %s requires a value\n", argv[i]); + std::printf("Usage: %s [--n|-n ] [--batch-size|-b ]\n", argv[0]); + std::printf(" or: %s \n", argv[0]); + return EXIT_FAILURE; + } + } else if (strcmp(argv[i], "--batch-size") == 0 || strcmp(argv[i], "-b") == 0) { + if (i + 1 < argc) { + batch_size = atoi(argv[++i]); + } else { + std::printf("Error: %s requires a value\n", argv[i]); + std::printf("Usage: %s [--n|-n ] [--batch-size|-b ]\n", argv[0]); + std::printf(" or: %s \n", argv[0]); + return EXIT_FAILURE; + } + } else if (strcmp(argv[i], "small") == 0) { n = 16; // 16×16×16 = 4K elements batch_size = 4; - size_name = "small"; - } else if (strcmp(argv[1], "medium") == 0) { + } else if (strcmp(argv[i], "medium") == 0) { n = 32; // 32×32×32 = 32K elements batch_size = 8; - size_name = "medium"; - } else if (strcmp(argv[1], "large") == 0) { + } else if (strcmp(argv[i], "large") == 0) { n = 64; // 64×64×64 = 262K elements batch_size = 8; - size_name = "large"; + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + std::printf("Usage: %s [OPTIONS]\n", argv[0]); + std::printf("\nOptions:\n"); + std::printf(" -n, --n 3D FFT dimension (n×n×n) (default: 16)\n"); + std::printf(" -b, --batch-size Number of batched 3D FFTs (default: 4)\n"); + std::printf("\nPresets:\n"); + std::printf(" small : n=16, batch_size=4 (16×16×16 = 4K elements)\n"); + std::printf(" medium : n=32, batch_size=8 (32×32×32 = 32K elements)\n"); + std::printf(" large : n=64, batch_size=8 (64×64×64 = 262K elements)\n"); + std::printf("\nExamples:\n"); + std::printf(" %s --n 64 --batch-size 16\n", argv[0]); + std::printf(" %s -n 128 -b 4\n", argv[0]); + std::printf(" %s medium --batch-size 16\n", argv[0]); + std::printf(" %s large\n", argv[0]); + return EXIT_SUCCESS; + } else { + std::printf("Error: Unknown argument '%s'\n", argv[i]); + std::printf("Usage: %s [--n|-n ] [--batch-size|-b ]\n", argv[0]); + std::printf(" or: %s \n", argv[0]); + std::printf(" or: %s --help\n", argv[0]); + return EXIT_FAILURE; } } @@ -60,7 +96,7 @@ int main(int argc, char *argv[]) { std::printf("==============================================\n"); std::printf("cuFFT 3D C2C Example (Scalable)\n"); std::printf("==============================================\n"); - std::printf("Size: %s (%dx%dx%d)\n", size_name, n, n, n); + std::printf("FFT dimension: %d×%d×%d\n", n, n, n); std::printf("FFT size: %d\n", fft_size); std::printf("Batch size: %d\n", batch_size); std::printf("==============================================\n\n"); diff --git a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp index a38ec3b25..d8551d9e9 100644 --- a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp +++ b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp @@ -37,12 +37,13 @@ int test_r2c_window_c2r() { - // Padded array for in-place transforms - float input_signals[batches][2 * complex_signal_size] = {}; - float output_signals[batches][2 * complex_signal_size]; - float reference[batches][2 * complex_signal_size]; + // Padded array for in-place transforms - use heap allocation for large sizes + const size_t array_size = batches * 2 * complex_signal_size; + float *input_signals = new float[array_size](); + float *output_signals = new float[array_size]; + float *reference = new float[array_size]; - init_input_signals(batches, signal_size, &input_signals[0][0]); + init_input_signals(batches, signal_size, input_signals); const size_t complex_size_bytes = batches * complex_signal_size * 2 * sizeof(float); @@ -106,14 +107,22 @@ int test_r2c_window_c2r() { CHECK_ERROR(cudaFree(device_params)); // Compute reference - if(reference_r2c_window_c2r(batches, signal_size, window_size, &input_signals[0][0], &reference[0][0]) != PASS_VALUE) { + if(reference_r2c_window_c2r(batches, signal_size, window_size, input_signals, reference) != PASS_VALUE) { printf("Failed to compute the reference"); + delete[] input_signals; + delete[] output_signals; + delete[] reference; return ERROR_VALUE; }; - double l2_error = compute_error(&reference[0][0], &output_signals[0][0], batches, signal_size); + double l2_error = compute_error(reference, output_signals, batches, signal_size); printf("L2 error: %e\n", l2_error); + // Cleanup heap-allocated arrays + delete[] input_signals; + delete[] output_signals; + delete[] reference; + return (l2_error < threshold) ? PASS_VALUE : ERROR_VALUE; } @@ -127,34 +136,79 @@ unsigned complex_signal_size = signal_size / 2 + 1; // Program main //////////////////////////////////////////////////////////////////////////////// int main(int argc, char **argv) { - // Parse size argument (small/medium/large) - const char* size_name = "small"; - if (argc > 1) { - if (strcmp(argv[1], "small") == 0) { + // Parse named command-line arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--batches") == 0 || strcmp(argv[i], "-b") == 0) { + if (i + 1 < argc) { + batches = atoi(argv[++i]); + } else { + printf("Error: %s requires a value\n", argv[i]); + printf("Usage: %s [--batches|-b ] [--signal-size|-s ] [--window-size|-w ]\n", argv[0]); + printf(" or: %s \n", argv[0]); + return ERROR_VALUE; + } + } else if (strcmp(argv[i], "--signal-size") == 0 || strcmp(argv[i], "-s") == 0) { + if (i + 1 < argc) { + signal_size = atoi(argv[++i]); + } else { + printf("Error: %s requires a value\n", argv[i]); + printf("Usage: %s [--batches|-b ] [--signal-size|-s ] [--window-size|-w ]\n", argv[0]); + printf(" or: %s \n", argv[0]); + return ERROR_VALUE; + } + } else if (strcmp(argv[i], "--window-size") == 0 || strcmp(argv[i], "-w") == 0) { + if (i + 1 < argc) { + window_size = atoi(argv[++i]); + } else { + printf("Error: %s requires a value\n", argv[i]); + printf("Usage: %s [--batches|-b ] [--signal-size|-s ] [--window-size|-w ]\n", argv[0]); + printf(" or: %s \n", argv[0]); + return ERROR_VALUE; + } + } else if (strcmp(argv[i], "small") == 0) { batches = 128; signal_size = 64; window_size = 16; - size_name = "small"; - } else if (strcmp(argv[1], "medium") == 0) { + } else if (strcmp(argv[i], "medium") == 0) { batches = 500; signal_size = 256; window_size = 32; - size_name = "medium"; - } else if (strcmp(argv[1], "large") == 0) { + } else if (strcmp(argv[i], "large") == 0) { batches = 2000; signal_size = 32; window_size = 8; - size_name = "large"; + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + printf("Usage: %s [OPTIONS]\n", argv[0]); + printf("\nOptions:\n"); + printf(" -b, --batches Number of FFT batches (default: 100)\n"); + printf(" -s, --signal-size Size of each signal (default: 128)\n"); + printf(" -w, --window-size Window size for truncation (default: 16)\n"); + printf("\nPresets:\n"); + printf(" small : batches=128, signal_size=64, window_size=16\n"); + printf(" medium : batches=500, signal_size=256, window_size=32\n"); + printf(" large : batches=2000, signal_size=32, window_size=8\n"); + printf("\nExamples:\n"); + printf(" %s --batches 1024 --signal-size 512 --window-size 32\n", argv[0]); + printf(" %s -b 1024 -s 512 -w 32\n", argv[0]); + printf(" %s medium --batches 1000\n", argv[0]); + printf(" %s small\n", argv[0]); + return PASS_VALUE; + } else { + printf("Error: Unknown argument '%s'\n", argv[i]); + printf("Usage: %s [--batches|-b ] [--signal-size|-s ] [--window-size|-w ]\n", argv[0]); + printf(" or: %s \n", argv[0]); + printf(" or: %s --help\n", argv[0]); + return ERROR_VALUE; } } + complex_signal_size = signal_size / 2 + 1; printf("==============================================\n"); printf("cuFFT LTO R2C:C2R Example (Scalable)\n"); printf("==============================================\n"); - printf("Size: %s\n", size_name); - printf("Signal size: %u\n", signal_size); printf("Batches: %u\n", batches); + printf("Signal size: %u\n", signal_size); printf("Window size: %u\n", window_size); printf("==============================================\n\n"); diff --git a/src/cuda/HPC/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu b/src/cuda/HPC/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu index 28667d97d..5253a00ae 100644 --- a/src/cuda/HPC/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu +++ b/src/cuda/HPC/cusolver/cusolver_Xgetrf/cusolver_Xgetrf_scalable.cu @@ -20,16 +20,21 @@ #include "cusolver_utils.h" void print_usage(const char* prog_name) { - printf("Usage: %s [size]\n", prog_name); + printf("Usage: %s [OPTIONS]\n", prog_name); printf("\n"); - printf("Arguments:\n"); - printf(" size Matrix size: small (16x16), medium (128x128), large (512x512)\n"); - printf(" Default: small\n"); + printf("Options:\n"); + printf(" -m, --m Matrix dimension (creates mxm matrix)\n"); + printf("\n"); + printf("Presets:\n"); + printf(" small 16x16 matrix\n"); + printf(" medium 128x128 matrix\n"); + printf(" large 512x512 matrix\n"); printf("\n"); printf("Examples:\n"); - printf(" %s small # 16x16 matrix\n", prog_name); - printf(" %s medium # 128x128 matrix\n", prog_name); - printf(" %s large # 512x512 matrix\n", prog_name); + printf(" %s --m 1024 # 1024x1024 matrix\n", prog_name); + printf(" %s -m 2048 # 2048x2048 matrix\n", prog_name); + printf(" %s small # 16x16 matrix\n", prog_name); + printf(" %s medium # 128x128 matrix\n", prog_name); printf("\n"); } @@ -37,25 +42,34 @@ int main(int argc, char *argv[]) { // Parse command line arguments int64_t m = 16; // Default: small const char* size_name = "small"; + bool custom_m = false; - if (argc > 1) { - if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { print_usage(argv[0]); return 0; - } - - if (strcmp(argv[1], "small") == 0) { + } else if (strcmp(argv[i], "--m") == 0 || strcmp(argv[i], "-m") == 0) { + if (i + 1 < argc) { + m = atoll(argv[++i]); + custom_m = true; + size_name = "custom"; + } else { + fprintf(stderr, "Error: %s requires a value\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } else if (strcmp(argv[i], "small") == 0) { m = 16; size_name = "small"; - } else if (strcmp(argv[1], "medium") == 0) { + } else if (strcmp(argv[i], "medium") == 0) { m = 128; size_name = "medium"; - } else if (strcmp(argv[1], "large") == 0) { + } else if (strcmp(argv[i], "large") == 0) { m = 512; size_name = "large"; } else { - fprintf(stderr, "Error: Unknown size '%s'\n", argv[1]); - fprintf(stderr, "Valid sizes: small, medium, large\n"); + fprintf(stderr, "Error: Unknown argument '%s'\n", argv[i]); + print_usage(argv[0]); return 1; } } @@ -63,7 +77,11 @@ int main(int argc, char *argv[]) { printf("==============================================\n"); printf("cuSOLVER Xgetrf Example (Scalable)\n"); printf("==============================================\n"); - printf("Matrix size: %s (%ldx%ld)\n", size_name, m, m); + if (custom_m) { + printf("Matrix size: %ldx%ld\n", m, m); + } else { + printf("Matrix size: %s (%ldx%ld)\n", size_name, m, m); + } printf("Pivot: ON (compute P*A = L*U)\n"); printf("==============================================\n\n"); diff --git a/src/cuda/HPC/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu b/src/cuda/HPC/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu index 4eb2e54ab..e5aded469 100644 --- a/src/cuda/HPC/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu +++ b/src/cuda/HPC/cusolver/cusolver_ormqr/cusolver_ormqr_scalable.cu @@ -21,16 +21,21 @@ #include "cusolver_utils.h" void print_usage(const char* prog_name) { - printf("Usage: %s [size]\n", prog_name); + printf("Usage: %s [OPTIONS]\n", prog_name); printf("\n"); - printf("Arguments:\n"); - printf(" size Matrix size: small (16x16), medium (256x256), large (768x768)\n"); - printf(" Default: small\n"); + printf("Options:\n"); + printf(" -m, --m Matrix dimension (creates mxm matrix)\n"); + printf("\n"); + printf("Presets:\n"); + printf(" small 16x16 matrix\n"); + printf(" medium 256x256 matrix\n"); + printf(" large 768x768 matrix\n"); printf("\n"); printf("Examples:\n"); - printf(" %s small # 16x16 matrix\n", prog_name); - printf(" %s medium # 256x256 matrix\n", prog_name); - printf(" %s large # 768x768 matrix\n", prog_name); + printf(" %s --m 512 # 512x512 matrix\n", prog_name); + printf(" %s -m 1024 # 1024x1024 matrix\n", prog_name); + printf(" %s small # 16x16 matrix\n", prog_name); + printf(" %s medium # 256x256 matrix\n", prog_name); printf("\n"); } @@ -38,25 +43,34 @@ int main(int argc, char *argv[]) { // Parse command line arguments int m = 16; // Default: small const char* size_name = "small"; + bool custom_m = false; - if (argc > 1) { - if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { print_usage(argv[0]); return 0; - } - - if (strcmp(argv[1], "small") == 0) { + } else if (strcmp(argv[i], "--m") == 0 || strcmp(argv[i], "-m") == 0) { + if (i + 1 < argc) { + m = atoi(argv[++i]); + custom_m = true; + size_name = "custom"; + } else { + fprintf(stderr, "Error: %s requires a value\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } else if (strcmp(argv[i], "small") == 0) { m = 16; size_name = "small"; - } else if (strcmp(argv[1], "medium") == 0) { + } else if (strcmp(argv[i], "medium") == 0) { m = 256; size_name = "medium"; - } else if (strcmp(argv[1], "large") == 0) { + } else if (strcmp(argv[i], "large") == 0) { m = 768; size_name = "large"; } else { - fprintf(stderr, "Error: Unknown size '%s'\n", argv[1]); - fprintf(stderr, "Valid sizes: small, medium, large\n"); + fprintf(stderr, "Error: Unknown argument '%s'\n", argv[i]); + print_usage(argv[0]); return 1; } } @@ -64,7 +78,11 @@ int main(int argc, char *argv[]) { printf("==============================================\n"); printf("cuSOLVER ormqr Example (Scalable)\n"); printf("==============================================\n"); - printf("Matrix size: %s (%dx%d)\n", size_name, m, m); + if (custom_m) { + printf("Matrix size: %dx%d\n", m, m); + } else { + printf("Matrix size: %s (%dx%d)\n", size_name, m, m); + } printf("==============================================\n\n"); cusolverDnHandle_t cusolverH = NULL; diff --git a/src/cuda/HPC/image/FDTD3d/FDTD3d.h b/src/cuda/HPC/image/FDTD3d/FDTD3d.h index bcab438f7..f943ab1a7 100644 --- a/src/cuda/HPC/image/FDTD3d/FDTD3d.h +++ b/src/cuda/HPC/image/FDTD3d/FDTD3d.h @@ -33,7 +33,7 @@ // take a very long time and you should avoid running on your // primary display in this case. #define k_dim_min 96 -#define k_dim_max 512 +#define k_dim_max 8192 #define k_dim_qa 248 // Note that the radius is defined here as exactly 4 since the From e1558189c90d59b5f0a23ee66dcc7fdb9e2374e4 Mon Sep 17 00:00:00 2001 From: Anunalla Date: Thu, 26 Mar 2026 12:35:18 +0000 Subject: [PATCH 08/11] VP- limit number of frames processed --- .gitignore | 3 +- .../vpi/vpi_background_subtractor/main.cpp | 276 ++++++++++++++++++ 2 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/cuda/HPC/vpi/vpi_background_subtractor/main.cpp diff --git a/.gitignore b/.gitignore index 2e94221d4..249eee225 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ src/cuda/GPU_Microbenchmark/ubench/**/* !src/cuda/GPU_Microbenchmark/ubench/**/Makefile # Ignore VPI symlinks -src/cuda/HPC/vpi/* \ No newline at end of file +src/cuda/HPC/vpi/* +!src/cuda/HPC/vpi/vpi_subtractor/main.cpp \ No newline at end of file diff --git a/src/cuda/HPC/vpi/vpi_background_subtractor/main.cpp b/src/cuda/HPC/vpi/vpi_background_subtractor/main.cpp new file mode 100644 index 000000000..9364f7eed --- /dev/null +++ b/src/cuda/HPC/vpi/vpi_background_subtractor/main.cpp @@ -0,0 +1,276 @@ +/* +* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of NVIDIA CORPORATION nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CHECK_STATUS(STMT) \ + do \ + { \ + VPIStatus status = (STMT); \ + if (status != VPI_SUCCESS) \ + { \ + char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \ + vpiGetLastStatusMessage(buffer, sizeof(buffer)); \ + std::ostringstream ss; \ + ss << vpiStatusGetName(status) << ": " << buffer; \ + throw std::runtime_error(ss.str()); \ + } \ + } while (0); + +int main(int argc, char *argv[]) +{ + // OpenCV image that will be wrapped by a VPIImage. + // Define it here so that it's destroyed *after* wrapper is destroyed + cv::Mat cvCurFrame; + + // VPI objects that will be used + VPIStream stream = NULL; + VPIImage imgCurFrame = NULL; + VPIImage bgimage = NULL; + VPIImage fgmask = NULL; + VPIPayload payload = NULL; + + int retval = 0; + + try + { + // Parse named arguments + std::string strBackend; + std::string strInputVideo; + int numFramesToProcess = -1; // -1 means process all frames + + for (int i = 1; i < argc; i++) + { + std::string arg = argv[i]; + + if (arg == "--backend" || arg == "-b") + { + if (i + 1 < argc) + { + strBackend = argv[++i]; + } + else + { + throw std::runtime_error("--backend requires a value"); + } + } + else if (arg == "--input" || arg == "-i") + { + if (i + 1 < argc) + { + strInputVideo = argv[++i]; + } + else + { + throw std::runtime_error("--input requires a value"); + } + } + else if (arg == "--num-frames" || arg == "-n") + { + if (i + 1 < argc) + { + numFramesToProcess = std::atoi(argv[++i]); + if (numFramesToProcess <= 0) + { + throw std::runtime_error("--num-frames must be a positive integer"); + } + } + else + { + throw std::runtime_error("--num-frames requires a value"); + } + } + else if (arg == "--help" || arg == "-h") + { + std::cout << "Usage: " << argv[0] << " [OPTIONS]\n" + << "Options:\n" + << " --backend, -b Backend to use (required)\n" + << " --input, -i Input video file (required)\n" + << " --num-frames, -n Number of frames to process (optional, default: all)\n" + << " --help, -h Show this help message\n"; + return 0; + } + else + { + throw std::runtime_error("Unknown argument: " + arg); + } + } + + // Validate required arguments + if (strBackend.empty()) + { + throw std::runtime_error("--backend is required\n\nUse --help for usage information"); + } + if (strInputVideo.empty()) + { + throw std::runtime_error("--input is required\n\nUse --help for usage information"); + } + + VPIBackend backend; + if (strBackend == "cpu") + { + backend = VPI_BACKEND_CPU; + } + else if (strBackend == "cuda") + { + backend = VPI_BACKEND_CUDA; + } + else + { + throw std::runtime_error("Backend '" + strBackend + "' not recognized."); + } + + // Load the input video + cv::VideoCapture invid; + if (!invid.open(strInputVideo)) + { + throw std::runtime_error("Can't open '" + strInputVideo + "'"); + } + + int32_t width = invid.get(cv::CAP_PROP_FRAME_WIDTH); + int32_t height = invid.get(cv::CAP_PROP_FRAME_HEIGHT); + + // Create the stream where processing will happen. We'll use user-provided backend. + CHECK_STATUS(vpiStreamCreate(backend, &stream)); + + // Create background subtractor payload to be executed on the given backend + // OpenCV delivers us BGR8 images, so the algorithm is configured to accept that. + CHECK_STATUS(vpiCreateBackgroundSubtractor(backend, width, height, VPI_IMAGE_FORMAT_BGR8, &payload)); + + // Create foreground image + CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_U8, 0, &fgmask)); + + // Create background image + CHECK_STATUS(vpiImageCreate(width, height, VPI_IMAGE_FORMAT_BGR8, 0, &bgimage)); + + int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G'); + double fps = invid.get(cv::CAP_PROP_FPS); + + cv::VideoWriter outVideo("fgmask_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height), false); + if (!outVideo.isOpened()) + { + throw std::runtime_error("Can't create output video"); + } + + cv::VideoWriter bgimageVideo("bgimage_" + strBackend + ".mp4", fourcc, fps, cv::Size(width, height)); + if (!outVideo.isOpened()) + { + throw std::runtime_error("Can't create output video"); + } + + // Fetch a new frame until video ends or desired frame count is reached + int idxFrame = 1; + + while (invid.read(cvCurFrame)) + { + // Check if we've reached the desired number of frames + if (numFramesToProcess > 0 && idxFrame > numFramesToProcess) + { + printf("Processed %d frames (limit reached)\n", idxFrame - 1); + break; + } + + printf("Processing frame %d\n", idxFrame++); + // Wrap frame into a VPIImage + if (imgCurFrame == NULL) + { + CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvCurFrame, 0, &imgCurFrame)); + } + else + { + CHECK_STATUS(vpiImageSetWrappedOpenCVMat(imgCurFrame, cvCurFrame)); + } + + VPIBackgroundSubtractorParams params; + CHECK_STATUS(vpiInitBackgroundSubtractorParams(¶ms)); + params.learningRate = 0.01; + + CHECK_STATUS( + vpiSubmitBackgroundSubtractor(stream, backend, payload, imgCurFrame, fgmask, bgimage, ¶ms)); + + // Wait for processing to finish. + CHECK_STATUS(vpiStreamSync(stream)); + + { + // Now add it to the output video stream + VPIImageData imgdata; + CHECK_STATUS(vpiImageLockData(fgmask, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgdata)); + + cv::Mat outFrame; + CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame)); + + outVideo << outFrame; + + CHECK_STATUS(vpiImageUnlock(fgmask)); + } + + { + VPIImageData bgdata; + CHECK_STATUS(vpiImageLockData(bgimage, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &bgdata)); + + cv::Mat outFrame; + CHECK_STATUS(vpiImageDataExportOpenCVMat(bgdata, &outFrame)); + + bgimageVideo << outFrame; + + CHECK_STATUS(vpiImageUnlock(bgimage)); + } + } + } + catch (std::exception &e) + { + std::cerr << e.what() << std::endl; + retval = 1; + } + + // Destroy all resources used + vpiStreamDestroy(stream); + vpiPayloadDestroy(payload); + + vpiImageDestroy(imgCurFrame); + vpiImageDestroy(fgmask); + vpiImageDestroy(bgimage); + + return retval; +} From b3b0f4810b1020c0b30eb8ca7cf6e16244b8a8e2 Mon Sep 17 00:00:00 2001 From: AnuNalla Date: Sun, 29 Mar 2026 21:37:13 -0400 Subject: [PATCH 09/11] Add 5090def, l2_bw ubench bug fix --- .gitignore | 3 + get_data.sh | 14 +- src/Makefile | 30 ++-- .../hw_def/blackwell_RTX5090_hw_def.h | 25 +++ src/cuda/GPU_Microbenchmark/hw_def/hw_def.h | 3 +- .../ubench/l2_cache/l2_bw_32f/l2_bw_32f.cu | 7 +- src/cuda/HPC/Makefile | 5 +- .../cufft_lto_r2c_c2r_scalable.cpp | 9 +- .../r2c_c2r_lto_callback_device.fatbin | Bin 2536 -> 0 bytes .../r2c_c2r_lto_callback_device_fatbin.h | 159 +++++++++--------- src/cuda/HPC/generate_large_ppm.py | 2 +- src/cuda/HPC/generate_large_signal.py | 2 +- src/cuda/HPC/get_dwt_data.sh | 2 +- src/cuda/HPC/get_image_data.sh | 2 +- src/cuda/HPC/image/FDTD3d/Makefile | 2 +- src/cuda/HPC/image/dwtHaar1D/Makefile | 2 +- src/cuda/HPC/image/recursiveGaussian/Makefile | 2 +- 17 files changed, 152 insertions(+), 117 deletions(-) create mode 100644 src/cuda/GPU_Microbenchmark/hw_def/blackwell_RTX5090_hw_def.h delete mode 100644 src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin diff --git a/.gitignore b/.gitignore index 249eee225..80ad9627a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ src/cuda/GPU_Microbenchmark/ubench/**/* !src/cuda/GPU_Microbenchmark/ubench/**/*.* !src/cuda/GPU_Microbenchmark/ubench/**/Makefile +# Ignore compiled CUDA binaries +*.fatbin + # Ignore VPI symlinks src/cuda/HPC/vpi/* !src/cuda/HPC/vpi/vpi_subtractor/main.cpp \ No newline at end of file diff --git a/get_data.sh b/get_data.sh index d2315eb9a..8654f9759 100755 --- a/get_data.sh +++ b/get_data.sh @@ -3,13 +3,13 @@ export BASH_ROOT="$( cd "$( dirname "$BASH_SOURCE" )" && pwd )" DATA_SUBDIR="/data_dirs/" DATA_ROOT=$BASH_ROOT$DATA_SUBDIR -# if [ ! -d $DATA_ROOT ]; then -# if [ ! -f $BASH_ROOT/all.gpgpu-sim-app-data.tgz ]; then -# wget https://engineering.purdue.edu/tgrogers/gpgpu-sim/benchmark_data/all.gpgpu-sim-app-data.tgz -# fi -# tar xzvf all.gpgpu-sim-app-data.tgz -C $BASH_ROOT -# rm all.gpgpu-sim-app-data.tgz -# fi +if [ ! -d $DATA_ROOT ]; then + if [ ! -f $BASH_ROOT/all.gpgpu-sim-app-data.tgz ]; then + wget https://engineering.purdue.edu/tgrogers/gpgpu-sim/benchmark_data/all.gpgpu-sim-app-data.tgz + fi + tar xzvf all.gpgpu-sim-app-data.tgz -C $BASH_ROOT + rm all.gpgpu-sim-app-data.tgz +fi echo "Generating HPC benchmark data..." if [ -f $BASH_ROOT/src/cuda/HPC/get_graph_data.sh ]; then diff --git a/src/Makefile b/src/Makefile index a4bbbbde3..c4c11872a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -559,27 +559,27 @@ hpc: git submodule sync && git submodule update --init --recursive # Build simple apps (cuFFT, cuSolver, image processing) - # $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple # Build graph apps (cuGraph submodule) - -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ - cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + #-$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ + # cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ # Build Newton apps (Newton submodule) - # cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ - # mkdir -p $(BINDIR)/$(BINSUBDIR)/external - # cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ - # bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh - # echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball - # chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball - # echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole - # chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ + mkdir -p $(BINDIR)/$(BINSUBDIR)/external + cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ + bash $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh + echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/diffsim_ball/example_diffsim_ball.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_diffsim_ball + echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole + chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole # Build VPI apps from symlinked sources and copy binaries - # $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi - # cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor - # cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector - # cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity + $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi + cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor + cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector + cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity clean_HPC: $(SETENV) $(MAKE) -C cuda/HPC clean diff --git a/src/cuda/GPU_Microbenchmark/hw_def/blackwell_RTX5090_hw_def.h b/src/cuda/GPU_Microbenchmark/hw_def/blackwell_RTX5090_hw_def.h new file mode 100644 index 000000000..fd178eaef --- /dev/null +++ b/src/cuda/GPU_Microbenchmark/hw_def/blackwell_RTX5090_hw_def.h @@ -0,0 +1,25 @@ + + +#ifndef BLACK_GB202_DEF_H +#define BLACK_GB202_DEF_H + +#include "./common/common.h" +#include "./common/deviceQuery.h" + +#define L1_SIZE (256 * 1024) // Max L1 size in bytes + +// #define CLK_FREQUENCY 2010 // frequency in MHz + +#define ISSUE_MODEL issue_model::single // single issue core or dual issue +#define CORE_MODEL core_model::subcore // subcore model or shared model +#define DRAM_MODEL dram_model::GDDR6 // memory type +#define WARP_SCHEDS_PER_SM 4 // number of warp schedulers per SM + + +#define SASS_hmma_per_PTX_wmma 2 + + +#define L2_BANKS_PER_MEM_CHANNEL 1 +#define L2_BANK_WIDTH_in_BYTE 64 + +#endif diff --git a/src/cuda/GPU_Microbenchmark/hw_def/hw_def.h b/src/cuda/GPU_Microbenchmark/hw_def/hw_def.h index 8d12b2456..227011654 100644 --- a/src/cuda/GPU_Microbenchmark/hw_def/hw_def.h +++ b/src/cuda/GPU_Microbenchmark/hw_def/hw_def.h @@ -14,6 +14,7 @@ // #include "volta_TITANV_hw_def.h" // #include "ampere_A100_hw_def.h" -#include "blackwell_B200_hw_def.h" +// #include "blackwell_B200_hw_def.h" +#include "blackwell_RTX5090_hw_def.h" #endif diff --git a/src/cuda/GPU_Microbenchmark/ubench/l2_cache/l2_bw_32f/l2_bw_32f.cu b/src/cuda/GPU_Microbenchmark/ubench/l2_cache/l2_bw_32f/l2_bw_32f.cu index 7ef201c6e..b279f8b49 100644 --- a/src/cuda/GPU_Microbenchmark/ubench/l2_cache/l2_bw_32f/l2_bw_32f.cu +++ b/src/cuda/GPU_Microbenchmark/ubench/l2_cache/l2_bw_32f/l2_bw_32f.cu @@ -96,12 +96,13 @@ int main(int argc, char *argv[]) } } + config.BLOCKS_NUM = config.SM_NUMBER * 2; // 2 blocks per SM + config.TOTAL_THREADS = config.THREADS_PER_BLOCK * config.BLOCKS_NUM; // Recalculate after changing BLOCKS_NUM + unsigned ARRAY_SIZE = config.TOTAL_THREADS + repeat_times * config.WARP_SIZE; assert(ARRAY_SIZE * sizeof(float) < config.L2_SIZE); // Array size must not exceed L2 size - - config.BLOCKS_NUM = config.SM_NUMBER * 2; // 2 blocks per SM - + // config.BLOCKS_NUM = config.SM_NUMBER * 2; // 2 blocks per SM // Commented out - causes mismatch on GPUs with MAX_THREADS_PER_SM != 2048 uint64_t *startClk = (uint64_t *)malloc(config.TOTAL_THREADS * sizeof(uint64_t)); uint64_t *stopClk = (uint64_t *)malloc(config.TOTAL_THREADS * sizeof(uint64_t)); diff --git a/src/cuda/HPC/Makefile b/src/cuda/HPC/Makefile index f8d1b267b..6ff6bd438 100644 --- a/src/cuda/HPC/Makefile +++ b/src/cuda/HPC/Makefile @@ -2,8 +2,8 @@ .PHONY: all simple graph newton vpi clean cufft cusolver image # Default: Build all apps (submodules initialized by parent Makefile) -# all: simple graph newton vpi -all: graph +all: simple graph newton vpi + # Simple apps (cuFFT, cuSolver, image processing - CUDA toolkit only) simple: cufft cusolver image @@ -98,4 +98,3 @@ clean: -rm -rf newton/newton_venv # Clean VPI apps and symlinks -rm -rf vpi/*/build - -rm -f vpi/*/main.cpp vpi/*/CMakeLists.txt diff --git a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp index d8551d9e9..97dd7f6f4 100644 --- a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp +++ b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/cufft_lto_r2c_c2r_scalable.cpp @@ -77,14 +77,19 @@ int test_r2c_window_c2r() { std::string callback_name = "windowing_callback"; #endif size_t lto_callback_fatbin_size = sizeof(window_callback); - CHECK_ERROR(cufftXtSetJITCallback(inverse_plan_cb, + printf("Setting up LTO callback '%s', fatbin size: %zu bytes\n", callback_name.c_str(), lto_callback_fatbin_size); + cufftResult cb_result = cufftXtSetJITCallback(inverse_plan_cb, callback_name.c_str(), (void*)window_callback, lto_callback_fatbin_size, CUFFT_CB_LD_COMPLEX, - (void **)&device_params)); + (void **)&device_params); + printf("cufftXtSetJITCallback returned: %d\n", cb_result); + CHECK_ERROR(cb_result); + printf("Creating forward plan (R2C): signal_size=%u, batches=%u\n", signal_size, batches); CHECK_ERROR(cufftMakePlan1d(forward_plan, signal_size, CUFFT_R2C, batches, &work_size)); + printf("Creating inverse plan (C2R) with callback: signal_size=%u, batches=%u\n", signal_size, batches); CHECK_ERROR(cufftMakePlan1d(inverse_plan_cb, signal_size, CUFFT_C2R, batches, &work_size)); // Transform signal forward diff --git a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device.fatbin deleted file mode 100644 index 24929dd3fc24cf1b0f2c07d055f7f6e83ea31061..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2536 zcmV<{900008009Ab0001B2><{9000192><{<0000$0096? z00000000000000HAOQdX0002V`9Mqn0000S3jhEB0000;0000e0000jW^{TzFd!{( za%E#*WNCIiF(55)a%E#*b8&KXJux6HW^G|)Jux5v000000002u*yW`LsjO&I-WGiZ z*EbC}Z}*r!g{9nJw|~04>RDN`Q%q$Vs`8(rU(8bev=rrH*TgJkhcft}{x9sjbm@~9AgdPaK@XtXkkfeNUT-hjz#`yi3k?-`9sSIXsQ6szG zQ=6--M>G=N2s5cunLun%zZS`K;tQ4HGcX~4BK5BXn%z%*WVbGFwYK4N`3sTjx1ZSv3Vw5O4YR}=3mH;S@Awn4#Y2^5^4L=FMlbtu7 z5BWCZNcoiPV{GCxHXs?fXn2$mr_O1tn6FA3!6yIqRz7tDBqOHDgHVxmDq5>Tu}-Dh zY-`ft)9U)jCl}jK`6>DeWnhJV>f>M+-}|Pq+M3;PN?=iQ5%xT6b1YhoJlRSbbJSCF zfq^qUO*4JwaW&~6by@3fKiyJs)wcQ^A)wimdMRKBd=vr9h^CzS^Elod-2og6YW#Arj7pX zN}&(e^+Yj+eGo|yjMPn>X9o$pyd|VYUgf@H1FWo1vrIM$Pdnno6O|`m&*qP@A}%Ud z^5u8tfzel5?C?_O^q!(r{PX@o=Vb%bCRbP7wa_Zw!Opr_h3XyhfUsrz1h{d6uIY}a zMe8#qeVPp)3^dQi99&#spcx1nuZIMpa%SvU8)?`GDj?6PE-`FrC!n&wx2KJ%r%^%7 zG|VMjCH75vSW9yeAcNL1*zNfEuvolqM5BBGx%iar+Y&?TzJ)LMe!~%nPcKG``Wgph zxV3-wr|=O&=p=ffkl zs2<3LAeoHGdCppa;_k7lgxT+hh_;6es+Nv^T>r3&31+SEwUDOL#D zJp`)W(^T(6i>83^? z)wL#xX2>l$@S_i6MiO%tR+B^iD+DfdX{%mMfsBTqn75}ot@69PWBYSCIZo1zQACa<0_78c6B-;oP4rxvcu{fCMBSbD*{!i1JdA;voLN99pMc`4{B9TwxPqsl8niS#HYVUl;1jT}EgnxP#3PWgBP>Kx zn#k8?VOg5xf~&>^eud!BtELg9V4-jg+aV^x+P4z8cBXCe)=WV;$7?!`CYD(e!}9%4 zEm@)tM9{MHIC2~qY#aYm%VLp!;@oyZT#l5(;?ML&K7*Ek@tsHy;T?g4Fh?s$Bht%B zKJL{MO#_zNk$3)Sc_hos&-isT>VS6z9OekNz;atXw516L}%g(~h}-nYwHdX_0BKP%JKVU^^zDmP=aHFfm)HhIjq@}j_C3U(yS zq9~tWw^TobGT0+1k-%Y} z-igF%Z^f(p=gJZhii{777=BrVy`GpcTiAN-87!$V9ainc4xJq%2z| z{8G?IU32q%yIiMAMG!L@%~7zPWLO%1K~??Z6F99~?&$PQUy^Ut=AZ^dO> Date: Tue, 31 Mar 2026 14:12:31 +0000 Subject: [PATCH 10/11] Cugraph make file bug fix --- src/Makefile | 8 +++++--- src/cuda/HPC/graph/mst_standalone/CMakeLists.txt | 12 +++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Makefile b/src/Makefile index c4c11872a..acb019d08 100644 --- a/src/Makefile +++ b/src/Makefile @@ -561,9 +561,7 @@ hpc: # Build simple apps (cuFFT, cuSolver, image processing) $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple - # Build graph apps (cuGraph submodule) - #-$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ - # cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + # Build Newton apps (Newton submodule) cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ @@ -581,6 +579,10 @@ hpc: cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector cp cuda/HPC/vpi/vpi_stereo_disparity/build/vpi_sample_02_stereo_disparity $(BINDIR)/$(BINSUBDIR)/vpi_stereo_disparity + # Build graph apps (cuGraph submodule) + -$(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC graph && \ + cp cuda/HPC/graph/mst_standalone/build/mst_standalone $(BINDIR)/$(BINSUBDIR)/ + clean_HPC: $(SETENV) $(MAKE) -C cuda/HPC clean rm -rf cuda/HPC/external/cugraph/build diff --git a/src/cuda/HPC/graph/mst_standalone/CMakeLists.txt b/src/cuda/HPC/graph/mst_standalone/CMakeLists.txt index f0c2dfb56..088a781ba 100644 --- a/src/cuda/HPC/graph/mst_standalone/CMakeLists.txt +++ b/src/cuda/HPC/graph/mst_standalone/CMakeLists.txt @@ -15,13 +15,20 @@ endif() add_executable(mst_standalone mst_standalone.cu) target_compile_features(mst_standalone PRIVATE cxx_std_17) +target_compile_options(mst_standalone PRIVATE $<$:--extended-lambda>) # Add include directories for cuGraph and dependencies target_include_directories(mst_standalone PRIVATE ${cugraph_ROOT}/cpp/include - ${cugraph_ROOT}/cpp/build/_deps/rmm-src/include + ${cugraph_ROOT}/cpp/build/_deps/rmm-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/rmm-build/include ${cugraph_ROOT}/cpp/build/_deps/raft-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/raft-build/include ${cugraph_ROOT}/cpp/build/_deps/cuvs-src/cpp/include + ${cugraph_ROOT}/cpp/build/_deps/cuvs-build/include + ${cugraph_ROOT}/cpp/build/_deps/cccl-src/libcudacxx/include + ${cugraph_ROOT}/cpp/build/_deps/cccl-src/thrust + ${cugraph_ROOT}/cpp/build/_deps/cccl-src/cub ${cugraph_ROOT}/cpp/build/_deps/rapids_logger-src/include ) @@ -31,6 +38,7 @@ target_link_directories(mst_standalone PRIVATE ${cugraph_ROOT}/cpp/build/_deps/rmm-build ${cugraph_ROOT}/cpp/build/_deps/raft-build ${cugraph_ROOT}/cpp/build/_deps/cuvs-build + ${cugraph_ROOT}/cpp/build/_deps/rapids_logger-build ) target_link_libraries(mst_standalone PRIVATE @@ -38,4 +46,6 @@ target_link_libraries(mst_standalone PRIVATE CUDA::cublas cugraph cugraph_c + rmm + rapids_logger ) From 1c8011c23d8bfd3cbb8891cc30126bf008c56aab Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Jul 2026 02:09:47 +0000 Subject: [PATCH 11/11] Stabilize Cugraph,Newton versions --- src/Makefile | 32 +- .../r2c_c2r_lto_callback_device_fatbin.h | 159 +- src/cuda/HPC/external/cugraph | 2 +- src/cuda/HPC/external/newton | 2 +- .../graph/mst_standalone/build/CMakeCache.txt | 758 +++ .../CMakeFiles/4.0.0/CMakeCUDACompiler.cmake | 89 + .../CMakeFiles/4.0.0/CMakeCXXCompiler.cmake | 103 + .../4.0.0/CMakeDetermineCompilerABI_CUDA.bin | Bin 0 -> 1002976 bytes .../4.0.0/CMakeDetermineCompilerABI_CXX.bin | Bin 0 -> 15992 bytes .../build/CMakeFiles/4.0.0/CMakeSystem.cmake | 15 + .../CompilerIdCUDA/CMakeCUDACompilerId.cu | 935 +++ .../CMakeFiles/4.0.0/CompilerIdCUDA/a.out | Bin 0 -> 1003384 bytes .../CompilerIdCXX/CMakeCXXCompilerId.cpp | 920 +++ .../CMakeFiles/4.0.0/CompilerIdCXX/a.out | Bin 0 -> 16096 bytes .../build/CMakeFiles/CMakeConfigureLog.yaml | 918 +++ .../CMakeDirectoryInformation.cmake | 16 + .../build/CMakeFiles/InstallScripts.json | 7 + .../build/CMakeFiles/Makefile.cmake | 64 + .../mst_standalone/build/CMakeFiles/Makefile2 | 122 + .../build/CMakeFiles/TargetDirectories.txt | 3 + .../build/CMakeFiles/cmake.check_cache | 1 + .../mst_standalone.dir/DependInfo.cmake | 23 + .../CMakeFiles/mst_standalone.dir/build.make | 121 + .../mst_standalone.dir/cmake_clean.cmake | 11 + .../compiler_depend.internal | 1710 ++++++ .../mst_standalone.dir/compiler_depend.make | 5119 +++++++++++++++++ .../mst_standalone.dir/compiler_depend.ts | 2 + .../CMakeFiles/mst_standalone.dir/depend.make | 2 + .../CMakeFiles/mst_standalone.dir/flags.make | 10 + .../mst_standalone.dir/includes_CUDA.rsp | 1 + .../CMakeFiles/mst_standalone.dir/link.txt | 1 + .../mst_standalone.dir/linkLibs.rsp | 1 + .../mst_standalone.dir/mst_standalone.cu.o.d | 1705 ++++++ .../mst_standalone.dir/objects1.rsp | 1 + .../mst_standalone.dir/progress.make | 3 + .../build/CMakeFiles/progress.marks | 1 + .../HPC/graph/mst_standalone/build/Makefile | 181 + .../mst_standalone/build/cmake_install.cmake | 66 + .../graph/mst_standalone/build/mst_standalone | Bin 0 -> 1107624 bytes .../graph/mst_standalone/mst_standalone.cu | 8 +- src/cuda/common/common.mk | 15 +- src/cuda/rodinia/3.1/common/common.mk | 15 +- src/cuda/rodinia/3.1/common/make.config | 12 + src/cuda/rodinia/3.1/cuda/b+tree/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/b+tree_k1/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/b+tree_k2/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/backprop/Makefile | 4 +- .../rodinia/3.1/cuda/backprop_k1/Makefile | 4 +- .../rodinia/3.1/cuda/backprop_k2/Makefile | 4 +- src/cuda/rodinia/3.1/cuda/dwt2d/Makefile | 4 +- src/cuda/rodinia/3.1/cuda/gaussian/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/heartwall/Makefile | 14 +- src/cuda/rodinia/3.1/cuda/hotspot/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/hotspot3D/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/hotspot_k1/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/huffman/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/hybridsort/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/kmeans/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/kmeans_k1/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/lavaMD/makefile | 2 +- .../rodinia/3.1/cuda/leukocyte/CUDA/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/lud/cuda/Makefile | 12 +- .../rodinia/3.1/cuda/mummergpu/src/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/myocyte/Makefile | 14 +- src/cuda/rodinia/3.1/cuda/nn/Makefile | 2 +- src/cuda/rodinia/3.1/cuda/nw/Makefile | 2 +- .../rodinia/3.1/cuda/particlefilter/Makefile | 36 +- src/cuda/rodinia/3.1/cuda/pathfinder/Makefile | 2 +- .../rodinia/3.1/cuda/pathfinder_k1/Makefile | 2 +- .../rodinia/3.1/cuda/srad/srad_v1/makefile | 2 +- .../rodinia/3.1/cuda/srad/srad_v1_k1/makefile | 2 +- .../rodinia/3.1/cuda/srad/srad_v2/Makefile | 2 +- src/setup_environment | 1 + 73 files changed, 13138 insertions(+), 151 deletions(-) create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeCache.txt create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCUDACompiler.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCXXCompiler.cmake create mode 100755 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CUDA.bin create mode 100755 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CXX.bin create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeSystem.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/CMakeCUDACompilerId.cu create mode 100755 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/a.out create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCXX/CMakeCXXCompilerId.cpp create mode 100755 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCXX/a.out create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/CMakeConfigureLog.yaml create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/InstallScripts.json create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/Makefile.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/Makefile2 create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/TargetDirectories.txt create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/cmake.check_cache create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/DependInfo.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/build.make create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/cmake_clean.cmake create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/compiler_depend.internal create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/compiler_depend.make create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/compiler_depend.ts create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/depend.make create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/flags.make create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/includes_CUDA.rsp create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/link.txt create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/linkLibs.rsp create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/mst_standalone.cu.o.d create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/objects1.rsp create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/mst_standalone.dir/progress.make create mode 100644 src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/progress.marks create mode 100644 src/cuda/HPC/graph/mst_standalone/build/Makefile create mode 100644 src/cuda/HPC/graph/mst_standalone/build/cmake_install.cmake create mode 100755 src/cuda/HPC/graph/mst_standalone/build/mst_standalone diff --git a/src/Makefile b/src/Makefile index acb019d08..8732b0717 100644 --- a/src/Makefile +++ b/src/Makefile @@ -546,24 +546,36 @@ clean_cutlass: ############################################################################### # Modern HPC Benchmarks - (CUDA 11+, sm_75+) ############################################################################### +# cugraph and newton both default to submodule commits that track their +# unpinned "main" dev branches. cugraph main pulls rmm/raft also at their +# unstable "main" tips via CPM, and those nightly branches routinely drift out +# of sync with each other (e.g. rmm removed rmm/mr/owning_wrapper.hpp upstream +# while cugraph main still included it), breaking the build for reasons +# outside this repo. Pin both to their latest tagged stable release instead. +CUGRAPH_TAG ?= v26.06.00 +NEWTON_TAG ?= v1.3.0 + hpc: mkdir -p $(BINDIR)/$(BINSUBDIR)/ # Initialize and update submodules (like cutlass pattern) - # Clean up any corrupted submodule directories (check if .git exists) + # Clean up any corrupted submodule directories (a normal, initialized + # submodule has .git as a FILE, not a directory - only treat it as + # corrupted if .git is missing entirely) @for dir in cuda/cuda-samples cuda/HPC/external/cugraph cuda/HPC/external/newton; do \ - if [ -d $$dir ] && [ ! -d $$dir/.git ]; then \ + if [ -d $$dir ] && [ ! -e $$dir/.git ]; then \ echo "Removing corrupted submodule directory: $$dir"; \ rm -rf $$dir; \ fi; \ done git submodule sync && git submodule update --init --recursive + cd cuda/HPC/external/cugraph && git fetch --tags --quiet origin $(CUGRAPH_TAG) && git checkout --quiet $(CUGRAPH_TAG) + cd cuda/HPC/external/newton && git fetch --tags --quiet origin $(NEWTON_TAG) && git checkout --quiet $(NEWTON_TAG) + # Build simple apps (cuFFT, cuSolver, image processing) $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC simple - - - - # Build Newton apps (Newton submodule) + + # # Build Newton apps (Newton submodule) cp -r cuda/HPC/newton $(BINDIR)/$(BINSUBDIR)/ mkdir -p $(BINDIR)/$(BINSUBDIR)/external cp -r cuda/HPC/external/newton $(BINDIR)/$(BINSUBDIR)/external/ @@ -573,7 +585,7 @@ hpc: echo 'source $(BINDIR)/$(BINSUBDIR)/newton/setup_newton.sh && python3 $(BINDIR)/$(BINSUBDIR)/newton/robot_cartpole/example_robot_cartpole.py "$$@"' > $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole chmod u+x $(BINDIR)/$(BINSUBDIR)/newton_robot_cartpole - # Build VPI apps from symlinked sources and copy binaries + # # Build VPI apps from symlinked sources and copy binaries $(SETENV) $(MAKE) $(MAKE_ARGS) -C cuda/HPC vpi cp cuda/HPC/vpi/vpi_background_subtractor/build/vpi_sample_14_background_subtractor $(BINDIR)/$(BINSUBDIR)/vpi_background_subtractor cp cuda/HPC/vpi/vpi_orb_feature_detector/build/vpi_sample_18_orb_feature_detector $(BINDIR)/$(BINSUBDIR)/vpi_orb_feature_detector @@ -585,9 +597,9 @@ hpc: clean_HPC: $(SETENV) $(MAKE) -C cuda/HPC clean - rm -rf cuda/HPC/external/cugraph/build - rm -rf cuda/HPC/external/newton/build - rm -rf cuda/HPC/newton/newton_venv + # rm -rf cuda/HPC/external/cugraph/build + # rm -rf cuda/HPC/external/newton/build + # rm -rf cuda/HPC/newton/newton_venv # clean_deeplearning: # $(SETENV) $(MAKE) $(MAKE_ARGS) noinline=$(noinline) -C cuda/cudnn/mnist clean diff --git a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device_fatbin.h b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device_fatbin.h index 4e97cd281..ae52cb068 100644 --- a/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device_fatbin.h +++ b/src/cuda/HPC/cufft/cufft_lto_r2c_c2r/r2c_c2r_lto_callback_device_fatbin.h @@ -3,87 +3,86 @@ extern "C" { #endif unsigned long long window_callback[] = { -0x00100001ba55ed50ULL,0x00000000000009f8ULL,0x0000007801010008ULL,0x0000000000000980ULL, -0x000000400000097aULL,0x0000004b00010040ULL,0x0000000000000000ULL,0x0000000000012011ULL, -0x00000000861d559cULL,0x0000000000000b38ULL,0x0000002800000048ULL,0x2d20303d7a74662dULL, +0x00100001ba55ed50ULL,0x00000000000009d8ULL,0x0000007801010008ULL,0x0000000000000960ULL, +0x000000400000095aULL,0x0000004b00010040ULL,0x0000000000000000ULL,0x0000000000012011ULL, +0x00000000450b41aeULL,0x0000000000000b18ULL,0x0000002800000048ULL,0x2d20303d7a74662dULL, 0x7669645f63657270ULL,0x636572702d20313dULL,0x20313d747271735fULL,0x20313d64616d662dULL, -0x0000000000000000ULL,0xb3df19853aa79a4eULL,0x9ce55e973efaf7b9ULL,0xfc5d4b5fbd7f7656ULL, -0xa16dd934438c2971ULL,0x73d0c26bafc12e47ULL,0xc55e34b6edfe113aULL,0x6a89003a1b8764a5ULL, -0xf1da8d1ac6137429ULL,0x2056217118c8564cULL,0xd408cb5fa0a53a2bULL,0xcbe5acb9331f5bfaULL, -0x462be6b0cca5934dULL,0x9b9e7c1e0e218196ULL,0xd873ec4c22f37e49ULL,0x02ebbd52b2810818ULL, -0xec2e1d2a87e77e0dULL,0x4915b0a280110c87ULL,0x1489d837223481b0ULL,0xcb43c090d76cf5c6ULL, -0xb22f9e00526ba48dULL,0xc680d59356482921ULL,0x0b0aeb2ecdc91c3bULL,0x7d84415d3f51f0d7ULL, -0x989832ace4dc2e90ULL,0x9b734532920eeedeULL,0xf31d2a7716674f31ULL,0x4ef6f384005e385fULL, -0x15a46b421f64a904ULL,0xcf084f6bd9e1e817ULL,0x9204f365d20a2ac4ULL,0xba35c48d37212d76ULL, -0x1997f360b753f06cULL,0x2f1b5cfe7ebc4108ULL,0xb30a4f954c82ef29ULL,0x8b5b329795581519ULL, -0xe012c44eaf3d9c4bULL,0x19a9d60149758779ULL,0x61e73f3af4a79265ULL,0x0c41dde210f2040eULL, -0xf65b08c5efa40305ULL,0xde45eeb929bad831ULL,0xc62fac341327b9a9ULL,0x3dc6a75c639e59e0ULL, -0xf0e2bf18f990e7bdULL,0x9e07cea9c0a2b240ULL,0x403f7fdf59a9f22bULL,0xf533989bd7551679ULL, -0xb972ad03a62d611dULL,0x5814c8f1029f9063ULL,0x935cd7350fb780c2ULL,0x0480baf83650c1daULL, -0xae6e283c826dab1bULL,0x9403eeecba31977eULL,0x171516ce797d87e7ULL,0x978abc930e89a78cULL, -0x8aec5f230461ebb8ULL,0xcf51594c1dcc07e4ULL,0x4e7c1b94b7d479a4ULL,0xd23236cd4982d9baULL, -0x69793ccdd0946bcbULL,0x2f667e3119cee955ULL,0xfe38643d463ac0baULL,0xc4f5842cbb9645b0ULL, -0xedf1793fd1e1ec67ULL,0x09651a6bfbc63253ULL,0x17e852707a4e1b4aULL,0x8ca32cfb49e9c1f2ULL, -0xe561e27972e37105ULL,0x6b17da7adb5c6dbdULL,0x859c087b1b46341dULL,0xc78b3aee1de06653ULL, -0x2296d389a9abff58ULL,0x8d44a92ae716d279ULL,0xdabae2b9cffe9335ULL,0xdd57f3e3a54e3f9aULL, -0x6fa10d4a75988453ULL,0x44751e637287cc4fULL,0x3826e90044b88618ULL,0xabdc86d1a00c4864ULL, -0xac5154b5269750faULL,0x0b0c98d0a734e6aeULL,0x8a38ebbe6dfcd890ULL,0xf578bc7e9f63584eULL, -0x066a20a40c81aa79ULL,0xb1da95c681714f98ULL,0x95a7a8939262db69ULL,0xeb5d6d162db0c47dULL, -0xf3cbe8f708242112ULL,0xaec2f3d2534d3653ULL,0xe368cdcad21cc38fULL,0x6ddf47e86c29ae6bULL, -0x217c3065a6c9fc31ULL,0x8cc86fed4c910837ULL,0x734aa81b3a35bdb4ULL,0xdfab9d5e3944c522ULL, -0xba8ace5aef0d926eULL,0xbca8fb8a3dd0dd71ULL,0x8c6f9b2ea76144a1ULL,0x3ab7c46491c5d372ULL, -0xfdea9370de1a3944ULL,0xb21ede18c16ec2cfULL,0xa247d0c8f3581cc0ULL,0xfb8a09a13694e5e5ULL, -0x2e3a35c923afdfbaULL,0x538bf2b326b6461dULL,0x6c38602ae2353a33ULL,0x0616b90c76d45fd1ULL, -0x8ba1ab4e2e5418a2ULL,0x6b1b4cba49aab043ULL,0xf4b3807b6202b50eULL,0x30e460e644adc04aULL, -0x2177e70736c1fc36ULL,0x08bc8cf5c8b924a4ULL,0x511edf6f6b2cafe8ULL,0xd50990da78d708e6ULL, -0x39bd58ef4af9f7d6ULL,0xc8d83cb7c862305dULL,0x0b64be09f791357fULL,0x203298c8a390b840ULL, -0xe8655af21c86b93dULL,0x0e9e081bebcb83f9ULL,0x7d4540d9fdae1387ULL,0xdc7ea2fccf583b79ULL, -0x58948df65e916276ULL,0x4fa699acc456d34eULL,0xa9bd264cb8c624b1ULL,0x6186bb1f42e517c7ULL, -0xf4d0cecdb6c77c86ULL,0x91b62496e92d4007ULL,0xe6884e4a995fbfc8ULL,0x360fb1f078911dbaULL, -0xd2913b43fc8ed09aULL,0x4802ac43e9cce4cbULL,0x51792bb1d9b9f6a5ULL,0x1b80a0a7c4f6aa74ULL, -0xe9cfe89ebf0fd497ULL,0x954e2ca2a5e42f12ULL,0xfde393fba24954f2ULL,0x570e5780e2d3f9faULL, -0x8f1352dda1f7ae5eULL,0xf3656cb562fd1ab6ULL,0x89d52ee3498587efULL,0x08ec08a1a6be2e49ULL, -0x2d8820f34153deefULL,0x579b418c80f79846ULL,0x996b578d31659089ULL,0x85b836c947893a33ULL, -0xe72e090c3cb8f0beULL,0xa4c03328e3c94489ULL,0xf6b569ca5ca0c462ULL,0xdd2e775be2369af4ULL, -0x8524d124d8aa721eULL,0xc0a77bc694326c48ULL,0xb6c1afa438fd842bULL,0xd7f290a76df49a2cULL, -0x99ead5386853ed6fULL,0x799b53798b539b1cULL,0x5db2e52ee8e56ee0ULL,0xfe08d75aec95f6aeULL, -0xfedbea28c256805cULL,0xdfd5f2a84e6bb1b3ULL,0x998a35c7cc4c6ea4ULL,0xd811df87ac37b46bULL, -0xe1a8cd605a7ee7a2ULL,0x6e50e24c7ad14c0bULL,0xb0939d317d59fbafULL,0x634ed5336389b361ULL, -0x6ffd0cac1457b393ULL,0xb051e7a7daad179cULL,0xa8717200aa075d3fULL,0x41deb9cd9acd8a15ULL, -0xeb41d554ef55aebcULL,0xa0d2661239c800b3ULL,0xbf327a02cef15065ULL,0xfba11541537dfec0ULL, -0x537d8e14e95a8fccULL,0x17ce1cb6524b5280ULL,0x022e06d043aeeaa5ULL,0x7e702426c779b0e5ULL, -0x5f3e81b3ea707e5aULL,0x07cad8b5ce0d8817ULL,0xc87601c78dac0445ULL,0x777611b269475022ULL, -0x3e24e81e1350d045ULL,0x253ecf00d8fbc0b9ULL,0x1178a94ef37c250eULL,0x007f3d52ecf1fcd7ULL, -0xe40a338a55e96fe9ULL,0x65fe06013586f9f0ULL,0x39552ceeab4b2881ULL,0xee7f86bd4d92ba59ULL, -0x3bb8faa518e27aecULL,0x9a3741260e2efcb7ULL,0x8dfa691ddb3a3482ULL,0x8e1565f9c1426e43ULL, -0x279a9a74fe66c9d1ULL,0x13b9f8925cc24f4fULL,0x43bf58cead91cef4ULL,0xd6e2b4a88629424dULL, -0xeee159c966933528ULL,0x9c4fb5ad60dc37b5ULL,0x17e808902199a72fULL,0x370ad041076053d1ULL, -0xf75706a61ae5a5deULL,0xd6386efeede5af4eULL,0xd63aa08aeabeb868ULL,0x621b3cb0fdbd7244ULL, -0xb471a2fc0bec5acfULL,0x20c318ab7abff16eULL,0x6c70f515eb8270b6ULL,0x15a67ea5468a3b9fULL, -0xac806d321b5f7729ULL,0xb6e8d71b32633a44ULL,0x1d81a3fc2e385b6cULL,0x6bc1e3c198e7a0a2ULL, -0xc6da0604ab91d760ULL,0xf35056b67187bf6dULL,0xf9b6eb751b88af0bULL,0xef422c4a4f7d915eULL, -0xa8f8324f46270989ULL,0xdd52d4b110c83432ULL,0xa25a603ec3ec6d75ULL,0x4c9b33515196921cULL, -0x02276767300c71c5ULL,0x457700ffba63c7f6ULL,0xcf16b3113812082fULL,0xe930a3e7b8729bf8ULL, -0xa9c1d4ae2411747fULL,0x20a008371ed9d342ULL,0x9205463540dd2ca6ULL,0x0f4ff6d7db127aa1ULL, -0xbd854c29ce1afbfbULL,0x1e8231fdfde87d45ULL,0x28efc8ad57411175ULL,0xad634e05d1009295ULL, -0xd7073fdea7279cc9ULL,0x920c15ac2619c0a1ULL,0x34f3b62f46bb053aULL,0x47496dd4845c853fULL, -0x14b46f3bfde59fb2ULL,0x1560522a21ca7458ULL,0x855978202eee5338ULL,0x0650162f164f2ee3ULL, -0x5bb43d678278fe99ULL,0x00e41b9de79c21abULL,0xbb814528fa67f1daULL,0xd9ead75fbb0924daULL, -0x11bcc3bdf9f2c518ULL,0x20162ada6a99bda9ULL,0xddb6d248728f7fafULL,0x2af54679064abca0ULL, -0x61be9c5fc3359a21ULL,0x67bab1acb7e2a71eULL,0x98ffd55efb24b2abULL,0x7bce9f79ae365a19ULL, -0x6eed62d19aaf43dcULL,0x678fe63ccb7f2d30ULL,0xdcf998ca5ef7076dULL,0xbd75e71fd6515355ULL, -0x93df67d27ddebc8dULL,0xa331ef2c4e33c37fULL,0xa1668f9cc53cf9fbULL,0x54eeaba4cce0e139ULL, -0xdc713dc459e8f295ULL,0xb1f751a14e827a95ULL,0xdc4c6b72e04299e7ULL,0xa45f439b44e5bcb0ULL, -0x75d528261a9dd346ULL,0x60bbe86e87d440d6ULL,0x5507f0a0f2f9d449ULL,0x589ddbe8d38c87d4ULL, -0x6a9325188b1b4e13ULL,0x6debafe7543805f4ULL,0x3c59cad53035933bULL,0x5425f2c2d6a01b15ULL, -0xe9e0f53e2e548128ULL,0xd6b40f8b54205c19ULL,0x0528cd0440ae1a58ULL,0xaa8d82f522379d6eULL, -0x8e96f1d46b2078acULL,0x7805c441a20ac004ULL,0x5704b4b0a0caa41dULL,0xc255c06a850c0d62ULL, -0xae2a53f4c3edd4ddULL,0x3e52c89c671196c5ULL,0xff37ac769ca776bcULL,0xb49a880feb53a57fULL, -0x350459f19159481eULL,0xd9cb47e6b7eeb44dULL,0x448720ab781460cfULL,0x67033b8e51fa51e6ULL, -0x135f62109a1a3bebULL,0xdadd7ecbe4238ad5ULL,0xcefe8f02b9ba9165ULL,0x59999f0427393ea1ULL, -0x02582b0a20ebe3f2ULL,0xe26080ea0fe5f050ULL,0x00851a6d480e2f34ULL,0xdcea9e2bab8481deULL, -0x0ae74fed789fb981ULL,0xeb5fa5b0f630d9aaULL,0xeab795f79c0fc9e0ULL,0x74810dca5d08ee93ULL, -0x00000000000078fbULL +0x0000000000000000ULL,0x078fe0100bc46db9ULL,0xd85103fc974e409fULL,0xbcb403c9a44dc689ULL, +0x314e4730bb4f8a72ULL,0x4872771520e9be20ULL,0x3547544226548f3dULL,0x678903a6ddc8694aULL, +0xef5abc1fd3d7be45ULL,0xe7a2712887cce8b6ULL,0xce68ff1357f03f7bULL,0xad12d0b6266a9584ULL, +0x8658856296c9bcdfULL,0xeac658692e9105c0ULL,0x75e265a73f7a7c8bULL,0x7d749ad9f61a5d85ULL, +0x53aa6b5a9722801bULL,0xe9f85dfd3827eedbULL,0xefd959d28e48a526ULL,0x15a525718dcc6289ULL, +0x0cfaa1d268d5a737ULL,0x37641cbe9cec3388ULL,0xc741480627fe44cbULL,0xa8d449033510c211ULL, +0x38850a35d6309ecbULL,0xa74d34b9efeecb70ULL,0xd00532864439ebe7ULL,0x43b651f29f317608ULL, +0xbe6b573a98b4ef86ULL,0x1d06f7bdcbcc92eeULL,0x8de3cd619c7f38f6ULL,0x270772a119beefe1ULL, +0xd6faa97e1ea9901cULL,0x2d11f21adbe2c11fULL,0x0e3dd56e55c6df8eULL,0x5f40c77ae1da9222ULL, +0x4cdd60c7fef6ceabULL,0xe836f9898247b4afULL,0x84cf1b8d49188a5eULL,0x2fbcd4f34f6981dcULL, +0xb499cfb56a8c2b40ULL,0xf8e41c69facfaf53ULL,0xf7d3d34da082fb61ULL,0x791500d6edb6aa23ULL, +0xaf37a68a3ae37dd0ULL,0x2b36fa1bb4bd8df0ULL,0x87e3862f2accb966ULL,0x0b463301ee0860d8ULL, +0x10d84a6633d1217eULL,0x6094225eafd7e19fULL,0x5ecc189b1baf758cULL,0x6c7b4cbabcb11c75ULL, +0x8724bc5595b728d1ULL,0xc9c10a5e625a7075ULL,0x4beeb287b36d1c9cULL,0x8c3874c7abcd2ff3ULL, +0x22f0d5833ae40149ULL,0xb63b5e55fae05543ULL,0xa66583d606ec6e1cULL,0xf6b4de0b6692da57ULL, +0xefd2c72611069facULL,0x434b7b0c10b84763ULL,0xafbbab4c6351ac69ULL,0x984c7feaa01a428eULL, +0xe5a972ad7653d2c6ULL,0x9babe995f52fc1e8ULL,0x6c93b0eabc2604beULL,0xeec0f1e288b7c734ULL, +0x5ca8ef25b195d9e4ULL,0x7e674fb238ca7bb4ULL,0xed454f36d1c7523eULL,0x66da3b0975217c26ULL, +0xb0ba2847ce3f1c09ULL,0xf0d7ec3cec9a7c86ULL,0x5accfb301092c0a0ULL,0x7ec4eb5c79b44245ULL, +0x5f18b54502089712ULL,0x196bcf473b46d33bULL,0xcfa601002996cf95ULL,0x5762a885b20a9136ULL, +0x3183aa02d9aacc87ULL,0x03bbafebaa06778bULL,0xc371e1c226cccac1ULL,0x3be5632827f845f0ULL, +0x207c1a05c0c2e9f5ULL,0xcd0ad4c76faa24e9ULL,0x6b65dd80f0923988ULL,0x9c73346dda9c4792ULL, +0x60414417ba2bd42eULL,0xb45b17529b6f5de8ULL,0xc377db85a914b2e9ULL,0xebd710deb33b9675ULL, +0x0bd522a10dc0cddaULL,0x24e223ca23cef957ULL,0x55849272a50e366cULL,0xd20c202fdf1f18a2ULL, +0x9dad4edfa08c9614ULL,0xf33b8babe5650c31ULL,0x24fc564975ee1145ULL,0xb735058c695d330cULL, +0x946c605f320f668eULL,0x8c872364350b107dULL,0x0d8e5f8520fd419eULL,0xb268b5b58ad1916eULL, +0xea440f7037e12ec5ULL,0x6de1d5ce327f0ba8ULL,0xc3ef814a9ce55576ULL,0x62e1926d5ba34795ULL, +0xa2c4a975e221d097ULL,0xb3589f852ea4139dULL,0x650ef8b9f21f3ecaULL,0x8c742005e80be354ULL, +0x65a0a75795abf1f1ULL,0xeb0c3432fe0fc6e7ULL,0x18afd0fdd24c42afULL,0x7fa1bda275a3308dULL, +0x4ea484e409c9baedULL,0x3e9d28a7a645930eULL,0x60e364847731ccd3ULL,0x4e11ce59c2011a69ULL, +0x9cffdeab3ce6bc70ULL,0x52fe4e190ce57155ULL,0x001cef4c81479380ULL,0xc800f81a0393c00eULL, +0xff219b8270f7ba30ULL,0x6324feb9c78c10a3ULL,0x033d1137d61e4ed3ULL,0x84cf22bfcb47d005ULL, +0x50fe3ff73b0c7ae9ULL,0x7a1c01fa893b632eULL,0xfa08a8b917b38f44ULL,0x2725aa9ba87cffe7ULL, +0x747a41e16f5827f0ULL,0x1db456394066f0bbULL,0x6d349fc29ad8011cULL,0x02beb8027daca41fULL, +0x22d1cc51b65369a1ULL,0x6690b47c9667ff03ULL,0xfa4d9535b520b146ULL,0x76d933d178505f69ULL, +0x22f95281757dfdf3ULL,0xfaa9f3ae7df0df48ULL,0x8599ad95dcd9494cULL,0x5f87109af79ad175ULL, +0xfe16b1ff77f6f4f3ULL,0x0d6a2973e1a93c76ULL,0x31dec2e07afa9d56ULL,0xcd08e17fd37745d9ULL, +0x895cf9fbbdc29260ULL,0x306b6fa8b8abf88fULL,0x9310a273ed2d5926ULL,0x39eefae89b56648cULL, +0xda79865bb6e21c2bULL,0x1cabc255f2b388a9ULL,0x7823178011935b4dULL,0x0e7691b368366532ULL, +0x5b07fefe7849126fULL,0xfa7a68f7391bd28fULL,0xd17e5a3379c93feaULL,0x515b1e35832b6916ULL, +0x7939e4cb6f99d630ULL,0xbb50fedea4215ba5ULL,0x4aea7cfee3ec713aULL,0x019be385cc64d894ULL, +0xdb4e7276d1784e2cULL,0x31d75bb15eaaf052ULL,0xec9be651b94c683bULL,0x3afd7cce42e8e4b4ULL, +0xc83ee305c627ec7bULL,0xf5329c527ae3ad9fULL,0x9db67f28bd7de402ULL,0x36136ea25d517e01ULL, +0x12c3acc6cebb3a3fULL,0xbda882c8606b9c82ULL,0x4325fa8683b92aabULL,0x21ddec316a79af06ULL, +0x65766c2b629d28d3ULL,0x7a595bbf6fbe36f2ULL,0x95517a95d893b24fULL,0x8004a626029187e7ULL, +0x4e5fa7bef577346aULL,0x17e325256dfe155aULL,0x0a8b45993264ad18ULL,0xd884894bec07a385ULL, +0x25a79e6476709c42ULL,0xad2f92f3b60abae5ULL,0x1e83efa52113ac8cULL,0xa617b3f544270b0dULL, +0xa6e0241543ba600cULL,0x06ca2b57e4548b5eULL,0xa7f65dddc21118d3ULL,0x2cef83d05a90eae5ULL, +0xebb87b588fc9e904ULL,0xe00b58b34d38ca9dULL,0xfac286241cd73bfeULL,0x17539919bc1eed60ULL, +0x7c50bf1cfeeb23a7ULL,0xfbb916b2389e65f0ULL,0xe635a2af9674993dULL,0xadc2fc7bc074e818ULL, +0xead1442cd165dedaULL,0xf0908d7300b8183aULL,0x6759e9e49b8110e6ULL,0x76e8021736d6140eULL, +0xd3725458bc281c38ULL,0xe1704b28a0914983ULL,0x2a6c30e2f3665045ULL,0x45f237b82a779ea3ULL, +0x060507c85cf58822ULL,0x5404f730de927113ULL,0xe4278f1046cca2adULL,0x22e7160b44e7fd06ULL, +0x572ce4ee6a7f8ac9ULL,0x3a85a10729d1d886ULL,0xced931583940f45dULL,0x27c6e7db795d14acULL, +0x73274196dd093195ULL,0x8885a9a33064056dULL,0xe324591a143f7db0ULL,0x355ebac8e1d284b3ULL, +0xf9199800564670c1ULL,0x28c54fd3a844cd00ULL,0x341548dcf9ff24d2ULL,0x4bf0f12181b9cf53ULL, +0xf026f3d205edaf9aULL,0xdb09f535db1286b3ULL,0x02f5a1bb4dbfbe07ULL,0xeba0f96ac6a18717ULL, +0xbfa46e088bd86264ULL,0xe4be05a743344e33ULL,0xb3ffb62ba7512e55ULL,0x17021ff52e84bdebULL, +0x80fd0037c9da5796ULL,0xed5b29c967ca39daULL,0x2ac1c6a18ebc61d5ULL,0x2459bcf496227634ULL, +0xfc43b2729d90d178ULL,0xdca8540bcc388257ULL,0x81cb2493ab1ae5a5ULL,0xed9199c6a243e7eeULL, +0xb812f03bb9f6ed85ULL,0xe54950b3850126cdULL,0x3d80f461370472f9ULL,0x264bdff581090ac7ULL, +0x9335fc2978a7201fULL,0xa6d31a9851bf5786ULL,0xa11b5139cdf871acULL,0x5f7a73a59ad94a10ULL, +0x47d57f5acd12b875ULL,0x6004a52cc21464abULL,0x61671225e0039126ULL,0xc2c3ea060e7351bfULL, +0x01b5c6d0dd0a9932ULL,0x076dd1b353442334ULL,0x8db3de2be07d02ceULL,0xa42e4031b409e27eULL, +0xca6ca0c230ba12cdULL,0x251906bc15d41b28ULL,0x5e25801bd57e1997ULL,0xc5bbbe3ab674260cULL, +0xe15b9502b78da0fcULL,0x85dd95125edd02a3ULL,0xe375a1ecc5484676ULL,0xd72294bc61445f1eULL, +0xd93b780b9d1a39d9ULL,0x85997b4f440ccdf4ULL,0x465051e31ddbd430ULL,0x8761f7adc0ea3998ULL, +0x0724975057a73c2cULL,0x9c569f90221c1559ULL,0x58f03188946d5d5aULL,0xaf06bab27692d7efULL, +0x4d7b7bb4b75c5b60ULL,0x75c9ba65c982db38ULL,0x3b6f0fe07a45b4c2ULL,0xba336002d6a33d36ULL, +0x82361f7b40cc10e0ULL,0x5c04a0b3f9dd7c36ULL,0x9aa0267497c5e79eULL,0xbb45a75ffbeac516ULL, +0xa2d8e1c2268da2e7ULL,0x169353f740d2cc63ULL,0x67f23c629dad9988ULL,0xe8352b5f86b919d6ULL, +0x0562c083713e73e7ULL,0xad657e6c80e3d528ULL,0x6e36a5a579bc411fULL,0x656b66f0df20b833ULL, +0xd127c98e59debe80ULL,0xf436e3aa37f18310ULL,0xff1d3c6dc3f65284ULL,0xce1806e074507dd4ULL, +0xa5ee4a737f938470ULL,0x38ad9e9353101428ULL,0x2058f468958dc934ULL,0x318a74d3e560c5a0ULL, +0x59d6a4f913f4c251ULL,0x6b22aa6d49cbddf0ULL,0x79fcb451dc004b4aULL,0x54cf6b56c419b0c3ULL, +0x0000000000007a6dULL }; #ifdef __cplusplus diff --git a/src/cuda/HPC/external/cugraph b/src/cuda/HPC/external/cugraph index 15e04b307..b0294312c 160000 --- a/src/cuda/HPC/external/cugraph +++ b/src/cuda/HPC/external/cugraph @@ -1 +1 @@ -Subproject commit 15e04b3070dda0985b281f69bc7d37ad0a91f94d +Subproject commit b0294312c108c137af26ba746a13189b09e18028 diff --git a/src/cuda/HPC/external/newton b/src/cuda/HPC/external/newton index 141baffff..ce11136b3 160000 --- a/src/cuda/HPC/external/newton +++ b/src/cuda/HPC/external/newton @@ -1 +1 @@ -Subproject commit 141baffff9d6059e94fbc399d9404868169555a8 +Subproject commit ce11136b3a28390944f7fe5a32801b31d8aa5670 diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeCache.txt b/src/cuda/HPC/graph/mst_standalone/build/CMakeCache.txt new file mode 100644 index 000000000..3fcd51bd2 --- /dev/null +++ b/src/cuda/HPC/graph/mst_standalone/build/CMakeCache.txt @@ -0,0 +1,758 @@ +# This is the CMakeCache file. +# For build in directory: /root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone/build +# It was generated by CMake: /root/.local/opt/cmake-4.0.0/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//Path to a program. +CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line + +//Path to a program. +CMAKE_AR:FILEPATH=/usr/bin/ar + +//Choose the type of build, options are: None Debug Release RelWithDebInfo +// MinSizeRel ... +CMAKE_BUILD_TYPE:STRING= + +//Enable/Disable color output during build. +CMAKE_COLOR_MAKEFILE:BOOL=ON + +//CUDA architectures +CMAKE_CUDA_ARCHITECTURES:STRING=52 + +//No help, variable specified on the command line. +CMAKE_CUDA_COMPILER:UNINITIALIZED=/usr/local/cuda-12.8//bin/nvcc + +//Flags used by the CUDA compiler during all build types. +CMAKE_CUDA_FLAGS:STRING= + +//Flags used by the CUDA compiler during DEBUG builds. +CMAKE_CUDA_FLAGS_DEBUG:STRING=-g + +//Flags used by the CUDA compiler during MINSIZEREL builds. +CMAKE_CUDA_FLAGS_MINSIZEREL:STRING=-O1 -DNDEBUG + +//Flags used by the CUDA compiler during RELEASE builds. +CMAKE_CUDA_FLAGS_RELEASE:STRING=-O3 -DNDEBUG + +//Flags used by the CUDA compiler during RELWITHDEBINFO builds. +CMAKE_CUDA_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//CXX compiler +CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ + +//A wrapper around 'ar' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_CXX_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-13 + +//A wrapper around 'ranlib' adding the appropriate '--plugin' option +// for the GCC compiler +CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-13 + +//Flags used by the CXX compiler during all build types. +CMAKE_CXX_FLAGS:STRING= + +//Flags used by the CXX compiler during DEBUG builds. +CMAKE_CXX_FLAGS_DEBUG:STRING=-g + +//Flags used by the CXX compiler during MINSIZEREL builds. +CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG + +//Flags used by the CXX compiler during RELEASE builds. +CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG + +//Flags used by the CXX compiler during RELWITHDEBINFO builds. +CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//Path to a program. +CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND + +//Flags used by the linker during all build types. +CMAKE_EXE_LINKER_FLAGS:STRING= + +//Flags used by the linker during DEBUG builds. +CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during MINSIZEREL builds. +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during RELEASE builds. +CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during RELWITHDEBINFO builds. +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Enable/Disable output of compile commands during generation. +CMAKE_EXPORT_COMPILE_COMMANDS:BOOL= + +//Value Computed by CMake. +CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/pkgRedirects + +//Install path prefix, prepended onto install directories. +CMAKE_INSTALL_PREFIX:PATH=/usr/local + +//Path to a program. +CMAKE_LINKER:FILEPATH=/usr/bin/ld + +//Path to a program. +CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/gmake + +//Flags used by the linker during the creation of modules during +// all build types. +CMAKE_MODULE_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of modules during +// DEBUG builds. +CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of modules during +// MINSIZEREL builds. +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of modules during +// RELEASE builds. +CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of modules during +// RELWITHDEBINFO builds. +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_NM:FILEPATH=/usr/bin/nm + +//Path to a program. +CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy + +//Path to a program. +CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump + +//Value Computed by CMake +CMAKE_PROJECT_DESCRIPTION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_HOMEPAGE_URL:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_NAME:STATIC=mst_standalone + +//Path to a program. +CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib + +//Path to a program. +CMAKE_READELF:FILEPATH=/usr/bin/readelf + +//Flags used by the linker during the creation of shared libraries +// during all build types. +CMAKE_SHARED_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of shared libraries +// during DEBUG builds. +CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of shared libraries +// during MINSIZEREL builds. +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELEASE builds. +CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELWITHDEBINFO builds. +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//If set, runtime paths are not added when installing shared libraries, +// but are added when building. +CMAKE_SKIP_INSTALL_RPATH:BOOL=NO + +//If set, runtime paths are not added when using shared libraries. +CMAKE_SKIP_RPATH:BOOL=NO + +//Flags used by the linker during the creation of static libraries +// during all build types. +CMAKE_STATIC_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of static libraries +// during DEBUG builds. +CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of static libraries +// during MINSIZEREL builds. +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of static libraries +// during RELEASE builds. +CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of static libraries +// during RELWITHDEBINFO builds. +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_STRIP:FILEPATH=/usr/bin/strip + +//Path to a program. +CMAKE_TAPI:FILEPATH=CMAKE_TAPI-NOTFOUND + +//If this value is on, makefiles will be generated without the +// .SILENT directive, and all commands will be echoed to the console +// during the make. This is useful for debugging only. With Visual +// Studio IDE projects all commands are done without /nologo. +CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE + +//Path to a file. +CUDAToolkit_CUPTI_INCLUDE_DIR:PATH=/usr/local/cuda-12.8/targets/x86_64-linux/include + +//Path to a library. +CUDAToolkit_rt_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/librt.a + +//Path to a library. +CUDA_CUDART:FILEPATH=/usr/local/cuda-12.8/lib64/libcudart.so + +//Path to a library. +CUDA_OpenCL_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libOpenCL.so + +//Path to a library. +CUDA_cuFile_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufile.so + +//Path to a library. +CUDA_cuFile_rdma_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufile_rdma.so + +//Path to a library. +CUDA_cuFile_rdma_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufile_rdma_static.a + +//Path to a library. +CUDA_cuFile_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufile_static.a + +//Path to a library. +CUDA_cublasLt_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcublasLt.so + +//Path to a library. +CUDA_cublasLt_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcublasLt_static.a + +//Path to a library. +CUDA_cublas_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcublas.so + +//Path to a library. +CUDA_cublas_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcublas_static.a + +//Path to a library. +CUDA_cuda_driver_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/stubs/libcuda.so + +//Path to a library. +CUDA_cudart_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcudart.so + +//Path to a library. +CUDA_cudart_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcudart_static.a + +//Path to a library. +CUDA_cudla_LIBRARY:FILEPATH=CUDA_cudla_LIBRARY-NOTFOUND + +//Path to a library. +CUDA_cufft_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufft.so + +//Path to a library. +CUDA_cufft_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufft_static.a + +//Path to a library. +CUDA_cufft_static_nocallback_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufft_static_nocallback.a + +//Path to a library. +CUDA_cufftw_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufftw.so + +//Path to a library. +CUDA_cufftw_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcufftw_static.a + +//Path to a library. +CUDA_culibos_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libculibos.a + +//Path to a library. +CUDA_cupti_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcupti.so + +//Path to a library. +CUDA_cupti_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcupti_static.a + +//Path to a library. +CUDA_curand_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcurand.so + +//Path to a library. +CUDA_curand_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcurand_static.a + +//Path to a library. +CUDA_cusolver_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusolver.so + +//Path to a library. +CUDA_cusolver_lapack_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusolver_lapack_static.a + +//Path to a library. +CUDA_cusolver_metis_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusolver_metis_static.a + +//Path to a library. +CUDA_cusolver_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusolver_static.a + +//Path to a library. +CUDA_cusparse_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusparse.so + +//Path to a library. +CUDA_cusparse_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcusparse_static.a + +//Path to a library. +CUDA_nppc_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppc.so + +//Path to a library. +CUDA_nppc_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppc_static.a + +//Path to a library. +CUDA_nppial_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppial.so + +//Path to a library. +CUDA_nppial_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppial_static.a + +//Path to a library. +CUDA_nppicc_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppicc.so + +//Path to a library. +CUDA_nppicc_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppicc_static.a + +//Path to a library. +CUDA_nppicom_LIBRARY:FILEPATH=CUDA_nppicom_LIBRARY-NOTFOUND + +//Path to a library. +CUDA_nppicom_static_LIBRARY:FILEPATH=CUDA_nppicom_static_LIBRARY-NOTFOUND + +//Path to a library. +CUDA_nppidei_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppidei.so + +//Path to a library. +CUDA_nppidei_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppidei_static.a + +//Path to a library. +CUDA_nppif_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppif.so + +//Path to a library. +CUDA_nppif_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppif_static.a + +//Path to a library. +CUDA_nppig_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppig.so + +//Path to a library. +CUDA_nppig_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppig_static.a + +//Path to a library. +CUDA_nppim_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppim.so + +//Path to a library. +CUDA_nppim_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppim_static.a + +//Path to a library. +CUDA_nppist_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppist.so + +//Path to a library. +CUDA_nppist_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppist_static.a + +//Path to a library. +CUDA_nppisu_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppisu.so + +//Path to a library. +CUDA_nppisu_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppisu_static.a + +//Path to a library. +CUDA_nppitc_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppitc.so + +//Path to a library. +CUDA_nppitc_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnppitc_static.a + +//Path to a library. +CUDA_npps_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnpps.so + +//Path to a library. +CUDA_npps_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnpps_static.a + +//Path to a library. +CUDA_nvJitLink_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvJitLink.so + +//Path to a library. +CUDA_nvJitLink_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvJitLink_static.a + +//Path to a library. +CUDA_nvToolsExt_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvToolsExt.so + +//Path to a library. +CUDA_nvfatbin_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvfatbin.so + +//Path to a library. +CUDA_nvfatbin_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvfatbin_static.a + +//Path to a library. +CUDA_nvgraph_LIBRARY:FILEPATH=CUDA_nvgraph_LIBRARY-NOTFOUND + +//Path to a library. +CUDA_nvgraph_static_LIBRARY:FILEPATH=CUDA_nvgraph_static_LIBRARY-NOTFOUND + +//Path to a library. +CUDA_nvjpeg_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvjpeg.so + +//Path to a library. +CUDA_nvjpeg_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvjpeg_static.a + +//Path to a library. +CUDA_nvml_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/stubs/libnvidia-ml.so + +//Path to a library. +CUDA_nvml_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/stubs/libnvidia-ml.a + +//Path to a library. +CUDA_nvperf_host_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvperf_host.so + +//Path to a library. +CUDA_nvperf_host_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvperf_host_static.a + +//Path to a library. +CUDA_nvperf_target_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvperf_target.so + +//Path to a library. +CUDA_nvptxcompiler_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvptxcompiler_static.a + +//Path to a library. +CUDA_nvrtc_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvrtc.so + +//Path to a library. +CUDA_nvrtc_builtins_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvrtc-builtins.so + +//Path to a library. +CUDA_nvrtc_builtins_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvrtc-builtins_static.a + +//Path to a library. +CUDA_nvrtc_static_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libnvrtc_static.a + +//Path to a library. +CUDA_pcsamplingutil_LIBRARY:FILEPATH=/usr/local/cuda-12.8/targets/x86_64-linux/lib/libpcsamplingutil.so + +//Value Computed by CMake +mst_standalone_BINARY_DIR:STATIC=/root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone/build + +//Value Computed by CMake +mst_standalone_IS_TOP_LEVEL:STATIC=ON + +//Value Computed by CMake +mst_standalone_SOURCE_DIR:STATIC=/root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone + + +######################## +# INTERNAL cache entries +######################## + +//ADVANCED property for variable: CMAKE_ADDR2LINE +CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_AR +CMAKE_AR-ADVANCED:INTERNAL=1 +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone/build +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=4 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=0 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=0 +//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE +CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/root/.local/opt/cmake-4.0.0/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/root/.local/opt/cmake-4.0.0/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/root/.local/opt/cmake-4.0.0/bin/ctest +//ADVANCED property for variable: CMAKE_CUDA_COMPILER +CMAKE_CUDA_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CUDA_FLAGS +CMAKE_CUDA_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CUDA_FLAGS_DEBUG +CMAKE_CUDA_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CUDA_FLAGS_MINSIZEREL +CMAKE_CUDA_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CUDA_FLAGS_RELEASE +CMAKE_CUDA_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CUDA_FLAGS_RELWITHDEBINFO +CMAKE_CUDA_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_COMPILER +CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR +CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB +CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS +CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG +CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL +CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE +CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO +CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_DLLTOOL +CMAKE_DLLTOOL-ADVANCED:INTERNAL=1 +//Path to cache edit program executable. +CMAKE_EDIT_COMMAND:INTERNAL=/root/.local/opt/cmake-4.0.0/bin/ccmake +//Executable file format +CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS +CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG +CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE +CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS +CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1 +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Unix Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Test CMAKE_HAVE_LIBC_PTHREAD +CMAKE_HAVE_LIBC_PTHREAD:INTERNAL=1 +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/root/accel-sim-framework-private/gpu-app-collection-public/src/cuda/HPC/graph/mst_standalone +//Install .so files without execute permission. +CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1 +//ADVANCED property for variable: CMAKE_LINKER +CMAKE_LINKER-ADVANCED:INTERNAL=1 +//Name of CMakeLists files to read +CMAKE_LIST_FILE_NAME:INTERNAL=CMakeLists.txt +//ADVANCED property for variable: CMAKE_MAKE_PROGRAM +CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS +CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG +CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE +CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_NM +CMAKE_NM-ADVANCED:INTERNAL=1 +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 +//ADVANCED property for variable: CMAKE_OBJCOPY +CMAKE_OBJCOPY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_OBJDUMP +CMAKE_OBJDUMP-ADVANCED:INTERNAL=1 +//Platform information initialized +CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_RANLIB +CMAKE_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_READELF +CMAKE_READELF-ADVANCED:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/root/.local/opt/cmake-4.0.0/share/cmake-4.0 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS +CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG +CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE +CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH +CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_RPATH +CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS +CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG +CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE +CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STRIP +CMAKE_STRIP-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_TAPI +CMAKE_TAPI-ADVANCED:INTERNAL=1 +//uname command +CMAKE_UNAME:INTERNAL=/usr/bin/uname +//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE +CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDAToolkit_CUPTI_INCLUDE_DIR +CUDAToolkit_CUPTI_INCLUDE_DIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDAToolkit_rt_LIBRARY +CUDAToolkit_rt_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_CUDART +CUDA_CUDART-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_OpenCL_LIBRARY +CUDA_OpenCL_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cuFile_LIBRARY +CUDA_cuFile_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cuFile_rdma_LIBRARY +CUDA_cuFile_rdma_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cuFile_rdma_static_LIBRARY +CUDA_cuFile_rdma_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cuFile_static_LIBRARY +CUDA_cuFile_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cublasLt_LIBRARY +CUDA_cublasLt_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cublasLt_static_LIBRARY +CUDA_cublasLt_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cublas_LIBRARY +CUDA_cublas_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cublas_static_LIBRARY +CUDA_cublas_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cuda_driver_LIBRARY +CUDA_cuda_driver_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cudart_LIBRARY +CUDA_cudart_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cudart_static_LIBRARY +CUDA_cudart_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cudla_LIBRARY +CUDA_cudla_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cufft_LIBRARY +CUDA_cufft_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cufft_static_LIBRARY +CUDA_cufft_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cufft_static_nocallback_LIBRARY +CUDA_cufft_static_nocallback_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cufftw_LIBRARY +CUDA_cufftw_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cufftw_static_LIBRARY +CUDA_cufftw_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_culibos_LIBRARY +CUDA_culibos_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cupti_LIBRARY +CUDA_cupti_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cupti_static_LIBRARY +CUDA_cupti_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_curand_LIBRARY +CUDA_curand_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_curand_static_LIBRARY +CUDA_curand_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusolver_LIBRARY +CUDA_cusolver_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusolver_lapack_static_LIBRARY +CUDA_cusolver_lapack_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusolver_metis_static_LIBRARY +CUDA_cusolver_metis_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusolver_static_LIBRARY +CUDA_cusolver_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusparse_LIBRARY +CUDA_cusparse_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_cusparse_static_LIBRARY +CUDA_cusparse_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppc_LIBRARY +CUDA_nppc_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppc_static_LIBRARY +CUDA_nppc_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppial_LIBRARY +CUDA_nppial_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppial_static_LIBRARY +CUDA_nppial_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppicc_LIBRARY +CUDA_nppicc_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppicc_static_LIBRARY +CUDA_nppicc_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppicom_LIBRARY +CUDA_nppicom_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppicom_static_LIBRARY +CUDA_nppicom_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppidei_LIBRARY +CUDA_nppidei_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppidei_static_LIBRARY +CUDA_nppidei_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppif_LIBRARY +CUDA_nppif_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppif_static_LIBRARY +CUDA_nppif_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppig_LIBRARY +CUDA_nppig_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppig_static_LIBRARY +CUDA_nppig_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppim_LIBRARY +CUDA_nppim_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppim_static_LIBRARY +CUDA_nppim_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppist_LIBRARY +CUDA_nppist_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppist_static_LIBRARY +CUDA_nppist_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppisu_LIBRARY +CUDA_nppisu_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppisu_static_LIBRARY +CUDA_nppisu_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppitc_LIBRARY +CUDA_nppitc_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nppitc_static_LIBRARY +CUDA_nppitc_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_npps_LIBRARY +CUDA_npps_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_npps_static_LIBRARY +CUDA_npps_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvJitLink_LIBRARY +CUDA_nvJitLink_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvJitLink_static_LIBRARY +CUDA_nvJitLink_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvToolsExt_LIBRARY +CUDA_nvToolsExt_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvfatbin_LIBRARY +CUDA_nvfatbin_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvfatbin_static_LIBRARY +CUDA_nvfatbin_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvgraph_LIBRARY +CUDA_nvgraph_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvgraph_static_LIBRARY +CUDA_nvgraph_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvjpeg_LIBRARY +CUDA_nvjpeg_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvjpeg_static_LIBRARY +CUDA_nvjpeg_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvml_LIBRARY +CUDA_nvml_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvml_static_LIBRARY +CUDA_nvml_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvperf_host_LIBRARY +CUDA_nvperf_host_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvperf_host_static_LIBRARY +CUDA_nvperf_host_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvperf_target_LIBRARY +CUDA_nvperf_target_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvptxcompiler_static_LIBRARY +CUDA_nvptxcompiler_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvrtc_LIBRARY +CUDA_nvrtc_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvrtc_builtins_LIBRARY +CUDA_nvrtc_builtins_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvrtc_builtins_static_LIBRARY +CUDA_nvrtc_builtins_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_nvrtc_static_LIBRARY +CUDA_nvrtc_static_LIBRARY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CUDA_pcsamplingutil_LIBRARY +CUDA_pcsamplingutil_LIBRARY-ADVANCED:INTERNAL=1 +//Details about finding CUDAToolkit +FIND_PACKAGE_MESSAGE_DETAILS_CUDAToolkit:INTERNAL=[/usr/local/cuda-12.8/targets/x86_64-linux/include][/usr/local/cuda-12.8/lib64/libcudart.so][/usr/local/cuda-12.8/bin][v12.8.93()] +//Details about finding Threads +FIND_PACKAGE_MESSAGE_DETAILS_Threads:INTERNAL=[TRUE][v()] + diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCUDACompiler.cmake b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCUDACompiler.cmake new file mode 100644 index 000000000..fc221e3d1 --- /dev/null +++ b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCUDACompiler.cmake @@ -0,0 +1,89 @@ +set(CMAKE_CUDA_COMPILER "/usr/local/cuda-12.8/bin/nvcc") +set(CMAKE_CUDA_HOST_COMPILER "") +set(CMAKE_CUDA_HOST_LINK_LAUNCHER "/usr/bin/g++") +set(CMAKE_CUDA_COMPILER_ID "NVIDIA") +set(CMAKE_CUDA_COMPILER_VERSION "12.8.93") +set(CMAKE_CUDA_HOST_COMPILER_ID "GNU") +set(CMAKE_CUDA_HOST_COMPILER_VERSION "13.3.0") +set(CMAKE_CUDA_DEVICE_LINKER "/usr/local/cuda-12.8/bin/nvlink") +set(CMAKE_CUDA_FATBINARY "/usr/local/cuda-12.8/bin/fatbinary") +set(CMAKE_CUDA_STANDARD_COMPUTED_DEFAULT "17") +set(CMAKE_CUDA_EXTENSIONS_COMPUTED_DEFAULT "ON") +set(CMAKE_CUDA_STANDARD_LATEST "20") +set(CMAKE_CUDA_COMPILE_FEATURES "cuda_std_03;cuda_std_11;cuda_std_14;cuda_std_17;cuda_std_20") +set(CMAKE_CUDA03_COMPILE_FEATURES "cuda_std_03") +set(CMAKE_CUDA11_COMPILE_FEATURES "cuda_std_11") +set(CMAKE_CUDA14_COMPILE_FEATURES "cuda_std_14") +set(CMAKE_CUDA17_COMPILE_FEATURES "cuda_std_17") +set(CMAKE_CUDA20_COMPILE_FEATURES "cuda_std_20") +set(CMAKE_CUDA23_COMPILE_FEATURES "") +set(CMAKE_CUDA26_COMPILE_FEATURES "") + +set(CMAKE_CUDA_PLATFORM_ID "Linux") +set(CMAKE_CUDA_SIMULATE_ID "GNU") +set(CMAKE_CUDA_COMPILER_FRONTEND_VARIANT "") +set(CMAKE_CUDA_SIMULATE_VERSION "13.3") + + + +set(CMAKE_CUDA_COMPILER_ENV_VAR "CUDACXX") +set(CMAKE_CUDA_HOST_COMPILER_ENV_VAR "CUDAHOSTCXX") + +set(CMAKE_CUDA_COMPILER_LOADED 1) +set(CMAKE_CUDA_COMPILER_ID_RUN 1) +set(CMAKE_CUDA_SOURCE_FILE_EXTENSIONS cu) +set(CMAKE_CUDA_LINKER_PREFERENCE 15) +set(CMAKE_CUDA_LINKER_PREFERENCE_PROPAGATES 1) +set(CMAKE_CUDA_LINKER_DEPFILE_SUPPORTED TRUE) +set(CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED TRUE) +set(CMAKE_CUDA_LINKER_PUSHPOP_STATE_SUPPORTED TRUE) + +set(CMAKE_CUDA_SIZEOF_DATA_PTR "8") +set(CMAKE_CUDA_COMPILER_ABI "ELF") +set(CMAKE_CUDA_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_CUDA_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") + +if(CMAKE_CUDA_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_CUDA_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_CUDA_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CUDA_COMPILER_ABI}") +endif() + +if(CMAKE_CUDA_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") +endif() + +set(CMAKE_CUDA_COMPILER_TOOLKIT_ROOT "/usr/local/cuda-12.8") +set(CMAKE_CUDA_COMPILER_TOOLKIT_LIBRARY_ROOT "/usr/local/cuda-12.8") +set(CMAKE_CUDA_COMPILER_TOOLKIT_VERSION "12.8.93") +set(CMAKE_CUDA_COMPILER_LIBRARY_ROOT "/usr/local/cuda-12.8") + +set(CMAKE_CUDA_ARCHITECTURES_ALL "50-real;52-real;53-real;60-real;61-real;62-real;70-real;72-real;75-real;80-real;86-real;87-real;89-real;90-real;100-real;101-real;120") +set(CMAKE_CUDA_ARCHITECTURES_ALL_MAJOR "50-real;60-real;70-real;80-real;90-real;100-real;120") +set(CMAKE_CUDA_ARCHITECTURES_NATIVE "80-real") + +set(CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES "/usr/local/cuda-12.8/targets/x86_64-linux/include") + +set(CMAKE_CUDA_HOST_IMPLICIT_LINK_LIBRARIES "") +set(CMAKE_CUDA_HOST_IMPLICIT_LINK_DIRECTORIES "/usr/local/cuda-12.8/targets/x86_64-linux/lib/stubs;/usr/local/cuda-12.8/targets/x86_64-linux/lib") +set(CMAKE_CUDA_HOST_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") + +set(CMAKE_CUDA_IMPLICIT_INCLUDE_DIRECTORIES "/usr/include/c++/13;/usr/include/x86_64-linux-gnu/c++/13;/usr/include/c++/13/backward;/usr/lib/gcc/x86_64-linux-gnu/13/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include") +set(CMAKE_CUDA_IMPLICIT_LINK_LIBRARIES "stdc++;m;gcc_s;gcc;c;gcc_s;gcc") +set(CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES "/usr/local/cuda-12.8/targets/x86_64-linux/lib/stubs;/usr/local/cuda-12.8/targets/x86_64-linux/lib;/usr/lib/gcc/x86_64-linux-gnu/13;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib") +set(CMAKE_CUDA_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") + +set(CMAKE_CUDA_RUNTIME_LIBRARY_DEFAULT "STATIC") + +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_LINKER_LINK "") +set(CMAKE_LINKER_LLD "") +set(CMAKE_CUDA_COMPILER_LINKER "/usr/bin/ld") +set(CMAKE_CUDA_COMPILER_LINKER_ID "GNU") +set(CMAKE_CUDA_COMPILER_LINKER_VERSION 2.42) +set(CMAKE_CUDA_COMPILER_LINKER_FRONTEND_VARIANT GNU) +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_MT "") diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCXXCompiler.cmake b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCXXCompiler.cmake new file mode 100644 index 000000000..9a0a73947 --- /dev/null +++ b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeCXXCompiler.cmake @@ -0,0 +1,103 @@ +set(CMAKE_CXX_COMPILER "/usr/bin/c++") +set(CMAKE_CXX_COMPILER_ARG1 "") +set(CMAKE_CXX_COMPILER_ID "GNU") +set(CMAKE_CXX_COMPILER_VERSION "13.3.0") +set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "") +set(CMAKE_CXX_COMPILER_WRAPPER "") +set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "17") +set(CMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT "ON") +set(CMAKE_CXX_STANDARD_LATEST "23") +set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23") +set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") +set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") +set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") +set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") +set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20") +set(CMAKE_CXX23_COMPILE_FEATURES "cxx_std_23") +set(CMAKE_CXX26_COMPILE_FEATURES "") + +set(CMAKE_CXX_PLATFORM_ID "Linux") +set(CMAKE_CXX_SIMULATE_ID "") +set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "GNU") +set(CMAKE_CXX_SIMULATE_VERSION "") + + + + +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_CXX_COMPILER_AR "/usr/bin/gcc-ar-13") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_CXX_COMPILER_RANLIB "/usr/bin/gcc-ranlib-13") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_LINKER_LINK "") +set(CMAKE_LINKER_LLD "") +set(CMAKE_CXX_COMPILER_LINKER "/usr/bin/ld") +set(CMAKE_CXX_COMPILER_LINKER_ID "GNU") +set(CMAKE_CXX_COMPILER_LINKER_VERSION 2.42) +set(CMAKE_CXX_COMPILER_LINKER_FRONTEND_VARIANT GNU) +set(CMAKE_MT "") +set(CMAKE_TAPI "CMAKE_TAPI-NOTFOUND") +set(CMAKE_COMPILER_IS_GNUCXX 1) +set(CMAKE_CXX_COMPILER_LOADED 1) +set(CMAKE_CXX_COMPILER_WORKS TRUE) +set(CMAKE_CXX_ABI_COMPILED TRUE) + +set(CMAKE_CXX_COMPILER_ENV_VAR "CXX") + +set(CMAKE_CXX_COMPILER_ID_RUN 1) +set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP;ixx;cppm;ccm;cxxm;c++m) +set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) + +foreach (lang IN ITEMS C OBJC OBJCXX) + if (CMAKE_${lang}_COMPILER_ID_RUN) + foreach(extension IN LISTS CMAKE_${lang}_SOURCE_FILE_EXTENSIONS) + list(REMOVE_ITEM CMAKE_CXX_SOURCE_FILE_EXTENSIONS ${extension}) + endforeach() + endif() +endforeach() + +set(CMAKE_CXX_LINKER_PREFERENCE 30) +set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1) +set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED TRUE) +set(CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED TRUE) +set(CMAKE_CXX_LINKER_PUSHPOP_STATE_SUPPORTED TRUE) + +# Save compiler ABI information. +set(CMAKE_CXX_SIZEOF_DATA_PTR "8") +set(CMAKE_CXX_COMPILER_ABI "ELF") +set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_CXX_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") + +if(CMAKE_CXX_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_CXX_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}") +endif() + +if(CMAKE_CXX_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") +endif() + +set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "") +if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}") +endif() + + + + + +set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "/usr/include/c++/13;/usr/include/x86_64-linux-gnu/c++/13;/usr/include/c++/13/backward;/usr/lib/gcc/x86_64-linux-gnu/13/include;/usr/local/include;/usr/include/x86_64-linux-gnu;/usr/include") +set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;m;gcc_s;gcc;c;gcc_s;gcc") +set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/usr/lib/gcc/x86_64-linux-gnu/13;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib") +set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") +set(CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR "") + +set(CMAKE_CXX_COMPILER_IMPORT_STD "") +### Imported target for C++23 standard library +set(CMAKE_CXX23_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE "Unsupported generator: Unix Makefiles") + + + diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CUDA.bin b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CUDA.bin new file mode 100755 index 0000000000000000000000000000000000000000..c0b0770853e80def2e9fb9f08cae8db58c898afd GIT binary patch literal 1002976 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
gKhPT;qw76`wIN4jSzvd8-PUBy!u4wH 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 z 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%0AWUZJAyv3#JtE>Dy_A z>6o9*!bU8;zEs$T>1nzYSg>k|Vj5f}sWw|Ha{5Bn>Q9rzEH_)|c|$S1F|#?F)>frU z^PTOO%qrv6Kt3yT9YwKE!xEvBEkE`}iw&HZukY$ChD(mdlHogOkQRC^_h`_WRXVu%;LzkcdZNoUa zy|Y6Z?_i~<3=V{VlZ&^W3@m0-sb1d2NQ?PHfyzyCwLV+ilFce{kVkP#&g9MpprdQd zYt)#^-VnoYk>2UdQ!rQUS=uaZTfbIxk(dK13@zEX+?HP;-=Z?2Yv(Id(!kU0C3%He zRf?-6ojR3@+wM{pV;rvJt4(JWs^d2(J=VW&^pYB9Y}kycXS*^8-hzR$HVBrnhYt>< zGl<$78><0_z4^|bnUaB{?`(mUEs`^ckMpUGA0-PryxHvQe70X5G%VHD~q|bP$M;ii?_kj?bb94+q4)4R7>M~ z#^!QDI=5LyVP#e=%_GB@Wz1unEfmBfJ@~;_2PQpew3FTcSxmD^}`>LPrVX-deJHmtL__(z$XN!Pvbs^J+>;e`m(E765*!cL0D+^G%2ZGD(QKpJIo&;p<76+8c)ZP=L^du@KbtAyz^S$@DxQb%WF|VLU?`!e>8{Yrc_AYqy*F>3Yoa;WY>9E-N)Or_ zgNe0pSFIw}+I%0V=uI2Ca_ci)vrJFTXsh>hL7BJv`VY2|b<=WImP)j)9k1u%F7ED7 zOKmxmDV3(18<<69$!?1xyhz)V49Q@$+?+ve zmgW~|3yP#ps5-x;72`yal)Me1Q9r~~a+_*X$|y74lii{@)TJ=3+kQw`D7%Tn_b0*P z?K9Oj+n@-$fh3f6yk0b$k^3F?{9?54t^qj1eJzRSk*}!-tFalO%lWBR&#N+!kd(A5 zh3AJ((e(r?UM~nwFOAfdx%zEf-sx;h%dQinJUV^%4D@hO46`&v^*vl^1>SCc28z=r zC8Qa?kJ}RPTwtjAs84FjC`UuFmF{&<8jCt4mf>h5W`gleFMiDmIZ%2`gNu&`?y3lb zF15!vlI7wGWoKojX9JVW_p2CvGx@4J78=vjH#2&B2~Af9wB9_5O|HajsJUyO@*Ak zAD3#+qS&`n7H&bR{)O%(`my1g`5sbA9B{u8O;6X=2>gi^Jy!3biW_;bJ)tD|z1(O*?@%1ZG^zBDd(*0#Kok5@572GW3?x&VOkeH;SI_ zY>tf3F+Eb&!Hj}wZob7vE-iSGgT2qg@G4gz?HWj~FhfR-7&1{w1A*<@)+VYYBQ>w) z3T9IDV})u7K|0q%PbSlxx*KCv-(I;5sJf%<-bl;Rdo6YXN*A~FqOgiyER4BE7}RHC z9Z(kz#v|q{xltGHj)<_vlz%$qNOA{HkXkA9Wieb{o8b;k>R@*l8gSBg=`{+(zV58Ce(Mq$Ke3~sx+Pt(_X=Glr z6F#KkJ7}@Y>a;eKPP8YvTiwH=k)6GF(@Hf?WqO7Scr{7Q@+I3{G8dp_Ri$({3vv5n z3gTC&-EvDRWpsYIc_NL{E*Iz)YCg@;SY%RUEa_0=PFr^A_o6g&sv6?ZmD+Vynx%lg zyDoJ>Cf_MNP2HDDwc9&ZY2CjV2sD_nB)17_rCw3k7*$mz$_*^_=hNBN@_g~_g|E@^ zom|3|wH66OjpV8MaCmyLgvuMgo@It*t>my7$-;+yfUTX~rEZyo*%qS>_vmc^jf--_ zpGjwwDmw+4Em*ah4>Zad($2G|Gp@5NwcV$Xo@Pa(cXecU6t&0R3)Wrbe~^z6B>7m8 zj*^eaNsnp4PM2L$+2Tn9%jVxuU`A%oU5iJ{3=I_m>VX2Nn9q9%B_(a-m8geSv>O_X zdhNtrl^a>E)oQ6qhK6dh+N#knsW7k0tvh$6j@}>%)26G9czbRZ7sbgNHY?VH-wK8pcK-H9{!Yu7Hnz#0fQ)u&Rezh8ahnAF77 zg0UK5Olr)SR(>ur_zTVNV^ZT&^G%$1lhQ$epO8|2jmEd$ImgbLb#&_RDaX#VquPR* zZ#s4+(%!(9QK5V78FP=FX@s^`jhbi9KbAk6{wVlaL}265Hb(o$a1$-YPaNz*A82S^ za@3eH^NukwN2P8~ZFkUA>M3*aZL7K1HhML;+OTeb?dP5 z*A_PRv~Nw#?Abh130i(g2T{n|wRalddiGwT4Ijsk zYUw|35RR1o<=~m7VM%J=WMtd9;7C5>4c3Xq7x{u}MgVYy; zq~B=LPc-SRXY;qd+~bs5wC&sCS>F=2zD+0O|6A!^ee~Jao{>s@i;P?64-FsU_x5v# zhR4$qcH_@JWlU-he(R;FRQkKo=QfK0)@vp>RgufSm9sZ&vL&LlAQ|FOR;1>)HAC~xw zhK5gCkV+kX+t6?`{-U)*!|%m!#ovv;Zr#xE4fwk=L&LwtKa4+tfv|P`(C~cxz4-0; zv)hJ-KZ$=O{%!aZ+lPk#g1;RD?tzbUNsr%*-G685)l8)9)P`p1275_zUn4 z%d%FaDyB)BfY@YA0e8h#3Y??1vv6a3?!gMSFW6@T47!H%Yn9e@0Jgg=KI@q6*x@h5&BIpXiZ|FMMsh4KpgO4|7{`1seM;fwGOeqm^M zC;sfKhK9d|zx!(Bh=1tI$gvsv|Bf8-XMbgAxQM^*+M(fV@F#wi_J^Owj|7hY4E|pH z87;{1YxE=hL-@P#d;f!WB4PZ)`0Mu2P8eVhz~B4-I6EKs*01^hXKn6XNw>um zMUhcf6hToGx%cMYn|p7!Wm~t^R$ELrwRIB|wRID_-IVoiL0M4*MNtGrQD0#-9UZY1 zHAO~EQ3SQ7N!s&!zE9Hhle2q!KlgVZkL}sKKCg4m`~3Z!&nKTGFbu;9#ufDZfO!w& za0U8)Nd3;o2V0@%PTCJf;0R2?Y0>Y(52Mi2Mmaym4->EldS>v$2%Lr~xD0)F(?1u` zkFW(genP*)2pp2vKgBPv;RU$F846bVeDu0JM{gWeupWz2*XM8y@>R`pq|io zFZG0x`-lUE=cp$P{gQk|{}pk!li#n28%E|?r(ozetW(f&KjTD>L+7i>@3*W|Fa&s3DHxX5dBz7!!dVzvCto=Z8#_6^!FmIICuh_EbW~;3Buv01 zn1p%gb!AlJ6~t4WQEe~^Bl7x`jEch;Ov&q0GphPZ;)gBJaau-o!7v<#X*eUt-5HgE zDd>4Ud{jmSVEFWm>V=L+XVjRyhI23ton54>$*3mieoRJn!Y~|!j>l%y6pX@Un1t1D zpuUeI4j8E=4w!~RF!uP2O2Ft7@Wap(GpgRCq|Fa~>I@JSgJgK;rE*Ft;36y)1%9N|}FR1~J+l)Qc=?GGKV zqJ7>%eV`AzeHq19t2m-?1g7CM4DO=dFb->am|weTPZ)#ZDzKIqv`{xA+>&>P68BuqfZ+oIoAt1cL{O8zx}_h7Qqw@*38?o!6}y)e6(F z4@S?*sB!2yoA!orSl!F(a~Lns7o>mW^|{O=d3_$^5r)oZ9>Hi^Mm4;H_PKy|hVBdL ze;9*P(0dW>028q8I^t`me3*iL(D!P_6^y}I=)RbF4>?swvc9WeX`;)W3zgWjv?KbU}y2ytCa`@skd z!W8U1$cH-i7atv?EN0@x#!Ymz_r!WFnp!;nZRezXtunl_Oo>BcU0ViOnm-dEnSlv(guo1f7K^!m$dtefd z%IoWBcNm3^nGjJ2+YGMbibSYVLgn)W|)AjFbP921$$r`4nW5{>0juEap-~5&vLv7==j~gNrZ@GcW4Xyhs`hnTVWE0U<&rYG#r4A!}LFN!#MQ7Y3POX&c^l=zTIjij z{tyn(A29qO+8;*X7>vRx7=uX|hl?-)GcXCOZ>L=7foa$T9e2?F&<#7F2Zo^+_Cp^W zhCw(ELoflua1KV`5{$w;j6rvl@?kwpz-E|)tuO^cFb#X4W0>}bZa4xxFb=(N8v5Wo z48mm?f{yo5KCFQe*Z`xj1;$_y#$hK+z+RYygD?eSFbyZ5|oTU>A(SJ{W^h7>A=U0ViP+&cYN-!8BZfj*l?^K0x`f4tk&$dZ8cs zU^@)LZWw|Q7=}YI0>@w!PQe&V!Z=)n37CONSUp7f&;!%32|7MX|3f$IfF2lzUf2(P za2N*RI1IrA48u7XflDw7^DqY8AEbO(4->E%CSfZ~!4OQt9_SdM|DhX>Ko5*VFPw%x zI1htx8HS+aLzE9|U<5Y6C~Sc-7=&@y2@|jvCgC7V!5B=#3F!D3{SV!60eWB>dZF_U z%7?Wu2peGt`d}Eg!3gYvQP>A#Fbd;v6ei##Ou|{1f+?7WE70+A`hS@6VIA~9FZ4n` z^ucx*gxxR%BQOkyU<8iAD4c>Zn1pe-2oo>^ld$^3ln*^H4V$3j6ZAiH!w%?yVd#bZ z&vRx7=uX|hl?-)GcXCOKTi431JkexIzCPRLpSVz9vFsR z*bjYh7zW`u48a5p!#Nm%OE3!aFb3V9pnO;l6R;U3VJl3*5KO}!=oqE{p&O1s4~#=E zoQ6I)4})+ShM*%x`LG5?U;~W878rv;7>AuO0efK*4#E_S!8DwJj?d8l&-K4?Qpqo1o)!^gndN4(Nel z=!N~z2Zv!0j>8a4z%ZPH5x4}SFb`wUJxcko9wuNjOu|-}f+3iOJ}FbQX23Z`Hhu0Y2Z=>N}AKCFWt z=!IVBhd$U2gRmQhU<8KY5RAYvnEo>D1XEw3ALKQ3kCA_zet==v3?pA9ADDzu==&P+ z!SuK2M;J^nK0Z%+>SPu5ftdVad5oa4j^1JE(MraEBk z7vwLzcTG)-ejnvP_pjGf)tBJ>nreda->j(~n1n;3-@m4&M2CyQ-xAN4i4)et^goGT z_^&n91wH9C)hDn2y{2Yi@IPxR1$_^#sm8C6FKmaA)ipH$qi_VqVH_slwCI^Nm4Uvs zHB}p@p0F9lU^|Rt*Hkx5!U%Nd)>I67-~UWzCNqE<@Jq}4-@aA zzA*Z3;*{gJW|e1>bkGl@x8aBJ_hr?Py#5G&=o`fk(_hM}`fn5GSBMvSC$cIE-IK%t zJ#Z2_zD?Y6{D)cP_zvv@YhY+Ps{$|qyP@|-lmnx1QaF=UOTwRKRqc1-&+rR>K|90v zy_5q}_hnTQI(|tzLhrA#s$q(H{yM8#VG8!Z$UN-{qYGIz1>-OU9ly(}>hBQ`Y=%+T z0pl@O1)tSj=%(*h2ekFZ$IGmf3vCydRG`XFa}4258)Gi72gl>!Di^o(=IUV$f-#f zugs|x=yv8*?VXgjBd3DU13RG)_QEKP!33OuDL4ZoC+AcKdLEHe4b$Y~%BgnfI3=eB zpyxEofoXS6EeTJjoF9=-O-==bk0pO$ZBC5|pO91Y(EX&Gs=f<8C8t_o_>7ziL*LVg z1A3pHQ%TX!%qho@X`g52loxuRol_y`cur15VeGj%m4H5PPNn7e3v$XcLtIVN6Q*C3 zQxTYWNlwLK=%qQ8f{vHxl>2U8x8#%$CSF1LF#0OWhml>BFWf`aG<-^EXlrKDo@_!1?rFC6;|Yex z7*Ejm1;&$bobd!BU(TtjIpT{GCyahAr@CPL8?-0%PSTz*_+5Ozgx|vlec#6?{6S6) z3-2UO;g5(DMt)43zruevaYD~eiBotFaYFaciBpdMBB$zpO}Y2c?lASsoa%>;dFB)J z+@Dj6!iAiwnJ3-vbIK2S15owA;2)_EOe|6#7=4g9e?$8%5hwKijX0rWnK*_2Bu-(P zIAQYN#Cbp2{g*g}|07PAUd^c~7|b$0M9<^hj9{ zd(u4xA9O!8uX*1Vb%{p`G|`XdbHRkQHC zyy}9nw!9jKsSERJ2D)FJR~hKLB(EC&L_fVIuR0(b3^fSDm*>?KOn2s08pdCrSM`hd zufhk@-FY=6yf&|Z}X^Q!tU)c-ws)grt#ueyat@@fPoZqKV(7=2$}Hlq+1KTp0Nq<)-0sUbVu+KWQK6{x|(7{4ebz{2%S}Z|b?q z_!X`(AA~vj5ysc)N1>u0{{v52S1r)tTvuV?&UF(12 z7<>A<>Vt_h*VQ=mJZoL0ps#UVxgX;AbJvw0ro8K_7Y1LruEt>WMeAw-I-1v2^(y(k zl={H*%hy#mbiZ<4jXna4@@1j01c$oUY=*`pzrru4xHz@bL_+aSBx*CGs+wsBR`_|P8jDLW7EAsge?E}5T z>uON=k##jC99dV(F!b?tRp&T~`+xMK@Kf|7jE%0VICOuGeuR~r7_%GrU{+oV28UB~?DEuGe5&Bjs7Y5hpXBf(@D@WBy zj_5l52NQ~Zf$@_zR1_wi8)^myPu@@&7=Gl2@;(AqZ>SKAor({p+#4zZJ&)c{X&8FU zhVnd;-G++8(33Y*3PwB|%I%{3r*0@ejMZM7u-ZTZj`z-b$P>_;%uaG;zFRL$$-?^&4scx^LW2 zQ_vgPP|L!bHdI{=?B7tW!do^}pKyS7hd!8q2^f70`XK!Peed5;4UeT9*aAZ^2*a=w zMqn?D!a?Z%DD{P2xGXxXejIVZdYFbS(D5c^HN6TKWOj!#He) z3D^pgFa%Su2d3cwbbOrrp$Eoc3{FGuCx{Qm;WG5aHk9M>#QiD88T7&sOu!MCf)mj3 zY2t$s=zIeCj&3L)bbN;TL*Hi^7xMZm^z#!*2Sd;oXCA;H9D<&&GQXe~F2D$M))619 zg9+FK(_f=L(EWAV6-MEV9G{>)pG5x910CO>Jz)}tq4(SLyXaHo`(*szBMzAQzYP_G z!36b(QJ98lSp5{@_#yRyzG?bj_#@g2hG0sL!xb2VRUY!ci#VYVw!#SPgfZ9)Lq8@R z^xVz*aR&Se^?_j+g9$hXQ*ar&e@eVhJ&EfQ<`WFV0QB8MyUJ^rgmIXLX;}R#$XSO|BmqhBYz;?Gii?$^8!X5VB84*NZiowPXqd&i3586LOIY2 zlh6kjVGw3u2v$FnbkGAMunDG@7+28ocjAB{n1M0qeirqD4KNM;(7jCj&5152K(EBg?5ys#wjKdU6z!jK+)s2)7Jus4Hd_ecV zS-)WrPQxf%gbA33X;||d>h&Muhi=#cJunD;up5S8KTN<$=>9MHz%X<_m*Xqc8%AIU zjKLn5fP*jzW6<$G(nB|#g&vrKVVH+eSo=Ku&<~>zF+N~qm30%QVG4RPtRr6P1#4j# zHo*uCz$om13D^Tua1c7yXbmQc>tW86Ql!l=8j^-ziE(U>Huq1Wdy;tbQ@aPgSY`#$W)3PE)EMhT$-bz;PIb z2^fQOFb#rANW_J z|IcUCt~<|h5}Su7R-arI<&fxg{1>fSRnHKQ|75qIp8uNouWREW_GbKXs(at5wFggg zy}c^pc-0eL{;U_)KNVG{?||Wx`HX`I#eb4#5C5I+#@L{x#&gPWoqBkB7F7GsGM_N&jT)@e8(&w{cwZf0p(5YqyT~aQsw`Uur#GQOH2o zpX2Qu_sH>G#rD~Cxbi}>XhfPQ9)EM?=J9=}?DXGWd7E?ZksW&ncOE)?a^=B_N8;Y+ z68|Fpu~S#o*S7J$-{P;S;1cunRrT@e&HN9YvU8v0e`Lp5gFDIH>6F~}x*U!MEPniB zPv$cWlv9d#_idGdBhJAchj&)K^`wGZVhQ7|^YHwCnfLa}y|+07M|SAUZ_@TdE{FJI z_(#rIRr_{W^4>pKdDwZeWO~R|>C=%1h(!iy693dQR+Us${>gYD^5Sf=scV8jyCW)1zwlxy!UWrNL>3}SMz`E z3*lQhj!)`2fUoA6e2&4I-|i!odk39?!#l2$T=%%%D7iXxyG-J5I|2U^{-G1_*E*R; zHvQ6e0sJ-3T2)t;#XndXIPAPag5T@9Mh7q69=z=~y)nEaHodcWQ#QRBy!Fqv7jHdd zugiv4`Z0)i952iB*0|WM$9G|J9yok*#pTi>0X;V(_!}FK<1ero6#kaytg1hi{QFPY$+$Y8$5q8`V%_h0v+f(o-@Vh}$lLVS zuuc}qJBGL8`S$V_e-i&J{&-p5o9jqL*+L?*$Op|^UvT_+;KtgBH;T7Q)<^z{=0jV4 z;g;q>YeNgXXjMI%zf0|Lp0vV2*FpX-o?bj{+jyidBX~l1F14=5#q$uSIX^@FfaEHR zas&QZ{LvS$s=N5RRIXpvd6v+M&P}TxGa0;1FIiRGcHQzGD4Y`tJ+{|Xp%?SQJZ)gF z<7{44hpqlXAGaRf(JOiHDo!Jve>?t8{P$b^oB8W}_qZzdO0q*PiG2Wn%}ZC+ersK~ z#=Q<+&bKmtC-64EjL#rj^WHl5=z%8fw}3B?(fpF1lqk&+L(Ds-=~ROpziAL(#- zUT)7XakSu1;y>Oz>cm&ovZ^jF#c}r58nM?^S|bMW`S4v;@&&c;brS6UE#Cyb0er_R zcL86btlWb-ze^>*-COxNsek4;`AHu&;`6>@Rb5w#kF`#>=UXMmfa^{CpJU=}$J>wh zTB~=rp5w0<^P%GTQ2MDK?+ji$>zDW^@K?QZRozsU_idGXk2v=Z?g$*-`9>+>fDT9e zi}<_n+s(hGn*H1f`2F~oPQc%Tzu{H(;~B-@Wz#QhKPU0yA1!O2!ur}OO|#Ecu?L9P zaSHwFJHEFOYa`w`Uc2qsfj@l${z3fSUH0>z!XLKj_Y(iI#E<_sQt!>{3ij!xaZ~Yj zX`NjzeU|mRB#q}(&Of{Dr;&DQBTdyFJ{xTa4;wnBL@_ zTNudVoyI$9!z*#8@pkNARX14kweC*?hfmh~$Nh!i` z5pP`w^x^dd?0LuV_8;B*u=R8f?+o6foU7Rg=yUa6>$%$bDAuC`_WQC4|G)|OJMk~! z50|yc=ANuT#{&b|TP zmDaop=Pm2jRL9wZ*K=r9ebDON+OzDF{yyaD;s1qwOE>-~{>!X>;?(DNzw25t$`~EO zH-qn|*1b?`ac_2qDB2(z zr)>RC+G7-L0L|{4ox?wG(=TN@YiO5q%j2j;t4FiO(TL_jvl~YT{;*BIjJ-kp3H-+! zdtSVgc%A31s!#BDX?-l6%UTcbtT?C8(t1>JUd6U7@ti;!X?xFO=zr3@#@eTrdu?2V zR95R7R)u?QLHyNi_WV-k2>zA}_}-mT{#>8?<$9N`+##y*d#T1gJr>9D`Yt@aSK^+> z>%VAKT~NxGy{ZxSP93*j--oDrEca#F?faYYkG^_Uoz1XUnhK z@xY*Li7UQdnA3Xu!3B@BeV#P&%U0E=9$v(j41Y0yKmH~B*O&P%`^xJi^L?(%`McHoq*ki7Al;dh9rN|9Iw6-O9Jl#-q%|5FPyL6CVBc@?fk!x?=oJ`6?}J* z%#lO#z5?$XTvRx(R@UmpKyIV!xl#KB=F^p{>T}k7t##BBsZd8h-saaE-mN-bP>4f^ z!x_7ff3M_EnxpI!^@b>5xnDerH)G~+jYZ#KJD}IGB;LL*`(w-TMA{Gk@#2ti+lbfm zhGWf(hwtM$@Q3hkSlh7t{?!i(ZBv-1gZRBy*^hk+e-wYeHGh3SLQl9uuD9r}*ZsMK zcg3c+hBu+wujc)nhvi$LFP{$-GnVn^!#iu!+l|-PZHyCZKVBoPv0I<(hVf3@^iJaq zT(hdaVvX0D@3oTeE}d^$;RPr37g&?ytQv! zRX3L9J6P#I>@3_8+*KI-vTu*!?Z$ha)q9lt>ofR9ZTQmo7LMbSv0nRRu0O-8>gA>L zHTTYp{ln{uONNYt0RH+n^SyZ1I9Z2U4?C}x*56ZjOH7Z&9=u(6@3wji_a%!1w^t8b zU4OjQZ?RE-eEu@u-mQC#`?l`&rSbLI@YOzr{w(ty{e2A|zGWM}E`0Sp$FD~eU&nEL z(m#{OpJV7H{!=J3UY9p33^ux2pbX!yCZcdBdvWvBItP zw&ZJ#w+C$6s@^d4IrIk1YLL^>pU{3HY1wui&@a{$2PxZ?W$m#y^MO?)aI(?|-*_ ze+K^qe!K18@C?TPfPH^E{vrIIu-U%@_?_>u@1Mlqga0Eo^IyWhcmn>~Ga3KyT~!~p znSTKPv`xRP@4fh&Ze3OHD$8HrC)cNqeXg+H1BrJO@30N8tW!z6dAzrl<-2*Wxnh^B zXnxD{9Xzg9L5jqdw;ya9uAQDc)|palB>MDTQnGilX;M@k-poc$;oFuLaBFw(c)x@DJg?xh#%j z`!o1g@R!{u+2;Jz@GRy-)V{wR{{;TB`yQoxNQLJQcDpKeZPs@HzyE!!>KkS8m&UDr zUID*f{4DK`N&Iv8x4SOV{g1Eu{r2x!yPwT`I03&8|M&^`!}#+j;E&;N{eb;`O-ejA z{nCyy*s6!NZ%6Sp;PW5HC+!{Ne|`AgBY8h;K677QzquIDtDE>E{BPj|{Bis(A6!*G zwB}!aU0TsDiyU{FWX`1|fBb*5`b*=mcrTM)f^H|S6&3BmdcPanUns`vZ?XE8}cNN~hYr&tyzf=7D z)AdK2MZ2^DLC45FR?B;fm6z(9CfsQ+jJXK@{AX6xFC~N0e9`w!Dh?J-)_NR|;hXp@ z-^*_Jc)qqcHuWRNI)*vCRiEQK@XNfLYiU?=-D`c8*y-hbjsK=HzhykXrI17(Z<0Q1 z!as@s+A_bMCl7lsK7==q*Us3NyrcMA#_ZK|3U3szoxG)QmhmU?+a3FL&*y&c=eKDW z%RHC$qZNMy|90mj@%7-F#P>h`w)T0D`+vm=tNU>TU-S$1%9r}h;!oT3OIzph2ft`< z?lruC{RQ3|%VORfQ+kuOr+CX=$KH;A5&tpAl%?$?_5u9& zd{g)q@I|dYV@~Tg&5CP=`-P0ZFCF`y?+r2)_7xTlZY4W>_($-+qLe@L{jGR-E#-$e zKE?5OmyUBE@fh#D>G6-h?#q0(#CjjavVK(jTLLINGdhLW{gvbQpX9rQw+-*OHIB1O z&sFPpQsvDb@zykP{|xV+tX^vjACwsO>-SrHct_%^>IYWu=9;WOEK;FYk3Ge_!}wdi zYWTO}cx_>&Da0{?H--0j=LU&+25j$c0+J1cm{@P@20Gj{a3<6vQBl)kNd5#{6C zBffKspTp5ls}|Po7LL2We*DjXbm9x(>odwL-0a`2?~Kb>8N?UEXJ{*`Db0G4@ zzkcv$mrLL4J*3~0T_MfPrnA&cD)c4=GCzHzq_g)XVX7}e{pJ6@iA#L z|I)Jz#pkGXJQ@6R-?Q&;Xr})Ex2p8V221-Cy>EP2{?cCU_~*V~{0!cve`}uWCrl3( z*Mxq&?Fr*ram%ZFZu7o=9PcvT3}ux1q129r&%apfnZiHy1N(mWOPPm1JeHq+;=^@? zM~impzV_koxN}vpt=9jf?rmtjX!nbb-;xWUecGCSb8Zzk zxble|8CMCsvv}>S88SB0_^W@ks!o%<`6ux@U&ekBEhfUz;?=A2J{_+YZ=aFgkJgL! z0c-lrc=abV4wlw}Fy3VwUa8v%-q2n4uYG6nC-B>83&~%8bI-7QCyjR*?{@oU@zuVPJcTgKFU;Si zxl_6i^YD8h!}#a$+qr&lljRKlrk|~;kVOP;$OjEb+7$+>V2G_@!QS6 z4gcf`OTANOh`QV?cVENlz2`c|C(LIGiNVDbLYIf4AW_*;HuKb{c&5&U-NeN^JX&({qdCH@10 zJLNeNeQ{HGCOUzC_SdWGUTb}=`*Qt^)^2^Rx{OzT8?tOqy?L!#+=5&8j&)r4g@0q; z--YPXz@m_cT-Z z`|y{|m6Bh7j(T^|uCEK+e%k*8{673Gzq6lz82`u#_+$9BifSw;k^RsVo0v91o)np?yb$&1UZSRwK z#+$V19mYHK z;5PZjw(_0E+y0m1du80F@zyQb-)ng2l5F~(^X4)p+wf1~Ki)M)AHK8=-zdI@zaKw8`Hk_A4c`jB;p6xks7Ku)#w)%@ThHg# zy@h_aYp=fE58$0%UR8YE-&UJiz4{sS!k(oE@611p&qp8SewV}$!#ns-!)wjgvcH+d z8~m5~ERE$lgU?LqcWT&7$T-O3kK=z2f0xEV>E0>t)$4Z_3r`}6zp<6`L)v(Le{)UJ zcl7qV^x)xUzxX@wM@_#m7Yc9IAJY4?LCOE$M*hd}OWRCI{`imbK4kIP;lgK)C6*<; zb9j$AZ;H2xdPp`W|Wke?NY^`S;>q#DC0ng1*x0M6oT$ z@caL3-#?Fk6u;egs?O#*c*WlHi89BV@%P|oDs7D&_D|=_Qs6H?Fe2U}TuHGt#^!hgF*{*%q^jMq0H;1q69*A}9$cOog z-@zvRo55fIKYM-|YYpd6U;Nxo-io6%SNw-}T0RfghIat(8_K+vc_^`*Bas)*_5Ju~ z@c-KCFMJ*%Ab>#L#FpbZ#W16seK zwV*`|Er`}WhoLz+QMVgf zEn1tQHKGL#&4<=%Xl-Z#L+e8G8(JTl&(NZ1ErvFV)@*2#XibJTi{>@76k4O9t)Mj+ zTGjc?MMJAY^B9^JthL%BFF|_Ko!g^s%>p@#Kd`)Oeh893uG_(%1l%a*u77VQ)ZQjs^(dGR z3A9;5n?sv1v?a7@L(8Kj49$H3=Nm(-N1HUXX0!=IYekD2S_p02(0b6u3~c~y)X+xI zVult+8!@zLv|&S=M;kJo>F(G&vtx$4d~c&(J#2dJU}? zt;f&?(ZYrnL+duQ3A8Rln?dU|v<0+~p{3C}49$5F>z|?3qO}=XBU;eVd}ytP)`k`^ zv@SHiq4lBp3@wV*VrZji&4xCK)?{e2XkJ50p*0%X3R;7qRkgGJ8Co5h$I!fJb%y3g zt2MNCv>LS2<%G;XIp=p5^ZTv!8mX{{+NVEH`#st1?AGry596IZyed(|$6#^RC%@e)e;$vL9HI{7%l4jnzC_($L(m zW<4>qdbAlsYet(kv{tl)p@q<<46O%k($EIbCJb!^EpBLWv~fe5MjJDD6GN*(8#c5Cv>`)lL5mt%5N*)VI?)CUtrxA|&<4>Wh89EXGqee`UPGHf>oK$i zw6LM2(Yg)Ic?s)@q1B>w8d@V-$k2Rf9fsD1)^2EBXl;hphZZ!nC|awbjiLn%Z4%9I zXtQWOLrbBx7}^S2v!PXWu$~xN9h%qByl9Pv=0|HVw05+5L+eKK7+M6a&d`R?Y7K1+ zt;W!%(Az|?3qsR*TkYXpLwg zL-V0^7+M=zyP9I{Adk^){a(hXx(TYLyMr*8QKt9t)Y#f)fn0on%mHlXw`aR#L!}B zeTFuH)@x`pXg!9ufEG5iG+MWzIj>;-GqhT?PD5)%3mKXZt;5jT(Ao{H3$4x2`p|-g z7Da0{v{AHxp-rOs4Q&?9XJ{$37DHP>Yc{m1D_Q>xtq#p=XkN5NL-V6G7+O18y`goZ zc?>OrR%d8KXtjnmhE`)}Q)q5OOQKa9+9Fz&p=HpVhE^>%z75TTmUkNKA6mxH0%$9S z)`6Bbv@qJTq4lFJ8QL(~qM?nWr3@{BwqR&;X!C})gf?erd9)}8AA)8tr%JdTH4UUXv>DykG5oJ!)S|!Hjb7u zv;^9Mq0OPq8`=`uoT25>l7{BKhV{?T>d|Hltr>0F&|1+Fh89AbGPEAFNkbbzn=rHy zw78+g(Z&sJ8g0zb=FvtCZ5b_QXpU=H{|v1LZP?Hn(1r}H1ubf5L9{_b>qHwcv|hA+ zLmNbk7+MUi&(J2&dJSy`t;f(7(87k6M(Z{-=Nno746PQe)6g2xLWbr;>oBx7w01-5 zLTfX$KD3~rMbTOfZ4@nFXp?AuLz_kO8CnXh#n4vJnhmWg%=%|&b!c8g^P)8xnjfvf z(Av@J4Xqo^V`veyIzt;mt2MMSv>HR3LUS8h60O?M7SXB5&&&S@(Y#d|emk8Po6jYdo^R2Q!SjW&AiAj4@zlHS&-xqgSd|Thw%M)3Z z9Xq6SK8)Igr3Js^w2Zp6>fwIBm3+5PtqH^&#z1@m*;N)+PQM!_&W-Q6GM+ z#bX_-(bFZ|y{^ixO8G;+{9M{PMY`ov))a4fZnZUIRzKQ!u<-nt_^RGY+*faG>#M`( z#g`|)7nJhjdu9t?y;2ze9IrcdO*K`Dp*a500*~6#Jm|e>JvD2V?}ec4`Ru-*?*Mr_ z@$3G_lftuZ9bdZtTaWB046s)?3$@nmIEz32q&4+YDY!I$^jy#nOUWEdCm(^o^ zUi&u5aF?q?K<7)E`nPd@eDa$5)v?lixX|L8X?*zFNmKKbwZd<(?vu5pmHKTyr_A$m z6&F#!e*GS8uzIKa@XlK*26r5B-d0)oAy!#8M#v*V9?z3<`6q1>M;k$VpLI?Y$5!DR zdhVBCO5Zb{!e9OLHIYvei#JgGz6Zvkd@0+$JMYr( zVSDk^J!4Is?8LK~pPn;*=FG?RoRQx=Xy9-MUdOZ6l%q7imTfR6-<=@+*ok-eIcw_a;^m+8V=vkSTE(Mzq1%x)kmvsn$oKya?mU|{ zVwY}AKBp#mjNu>ht*M9jTmFeQg%;Vhrq0kxA>Sn02-@A&HfLUOKB&AyXBcql=vW}6 z-5l>=KY05?mibJ(wH4Y;%J6W!ZclMc@H)VMO=w+cf0a3Ypg2zSyxjaMgvzJtMQD%f zd~(`bcn~2Vqx@w0Z0C_3w>fVot+Zo=ysG!FsoVIw)GzvXEA%4~#jgb{)NLGZ81HAT z-qJIZdi3j0hRE6{u`l3H%<=U-Q9{l z83}t``sbYduHqPzwrnL$G_a;VFL_@e?Lwcv19RXm-PYY4_Z(PL&z0kwbBD3jiAB;6 za@@=DGp)zv3ru(|v5#}y$MJbN&Ogx-Xw?VT)LyL==E59W0PPi`>2l6FWv4t$%1cSJ z!tpMS%M_M>Qg+q#j8n9ii?A8n+X`uR@GV z&2fC5<1gL0d7Qaf`W@rS$M2MoTlG}d?cwZWeK>zj{a5nipR5tJXmf3AYScQ9HlOv+ zNd^H|g&u7Im;TTp=W-wEf~0G`a7}&5S~g?!;B8J$TKe~$uhF~8{jSOv>pyn6Dkt^C zl0@UR&sb%LOv_rN8FMqy92ueX^YA^u)p} zec-%-@sD?kzf0v6_MI&1g>R?fYk?$t9iy~yGimxmYw9=En`!voCrkd7|JA|iHBkIr z_?xa-EB_wC(r0cilDrSPir=#)F%9Ei!T&jH`qoVh4yydNSnQBP5GoQo4*TUt;@+3cn=+G8Pk0hAdmVRXb+iG`dVUZ&lecn zxu5m?MyFKr7`aFr4U$LVU7Qmn58dzVt+w`!e7&Fk8c?aOK8H--OfjDy^S|&lFZZMz@j-BVC5)ot$T)YwF@sOq8L=BEQeX zHm354dIu)kWbF^)UmjXhH?%X}Q`<~rD+jSQSut@wq(u_>3sW+9Cp+94=@5qkg=e70sqU?9c7!BiJ z{>EDQZT9VdIQI?9>H z-#NLap2pvWc4dCb*_f9ScP7IAo8u>!#jW?Jg*Yl6lnC@bQv40?;`-p*YwAUh;KkAsQcP7g8n;9a_R zO_~o{7KqF#FpF*3`JQzsu%j@Db7t(qQ5b;P?OCo?pgCujIe7rUp+k<2j&z|3W_A ztXI4Ju8MIfsCX?FCtck`Yii889$D8gnF@{i#463L&+m(*8P2Y$R3$=D%b;~`Z0&*c z{O{-fCfaYrwAn_5yf-(to8PTl(M3pZY(COdt2OmZX(#@P)`r$ll`ZU3c`bQ&p-rQ` zvsB+h#qn_gr&xU@L{0xHHI*r>$H@q3+>gv!K1WI#*>mY{fi7IvB=EH2xtcVEnDG?O z-Q2{e&=->)4lUsu!gpE8$8S(eUT0Gxn)G4y&D;;eld0f^u0I~?f3{qc6uw$)ZZkmf zx8RSRl2xlEzm%=lJ_c1G!HguJC&b^4KkT-j{|J8HqwVD{Yr!mj_hYlFrqsp~|8146 zN1SKr_5QDgx{5c0*ZH`t<@fSRv7b}ir#<2%?BcDzh5d1DRz3Du`Tl(?-yq&R-shFP z56ib&^7W8!A6~~3vX&kyM7ve)Io=6*Nr_>`=YzYA%&hSA?) z=6CI(IBAb2JTrI#;^~k*JHJydPn_?)OD^(msn}CEL5eqocNuSc$$MVGd+09N)|b2< zjt}DXc(O{;IsC{iNkT8lzCYpzQA1} zGrmT=v2Duskwp;C2%dLI|6g3Zo+(>5pCR3U%xhxFV~9MK&iMb&qmw-5$)o9MS+!r{ zXqPz7I%TJvT#8+E;UlGw4!TTs$(Rbb&eMm_=2n5&jzW>89N&9bkL$DQ94SZNw_&_k z%6YZU;2c+h`%5gAbAdj5X!~+x?HHpR=hL$)#NVZ{%=&eM#C!JUtt1(QT$^B-$KQni zeye};zUQvZTOj(ngCB~QO!k+4FTUozq$lkur8&&GeDm6`Q@rQ6^q13cPV$q#AOGYt zvg$EbKkLpuz1P`$c&Gks_3pxT;(nL@7SXd@#f@I!=YjQn9VDOVnc4F1&#{i33jKXG zXKii{rCk!F8EnX^3rh85pGUjsA`iIsOSHROg%gJO)A*(-~WPa`R_QMC($1)bk^Cfvx~3L3hUbxX%Z)pM%SM-dD1*o)>rzRrO)wl zbN(HAVce_t>z+Z@!zSh;F*@{pT)uPtVm)dPxGo@+L&XVI+{VfYSL)MFy80It(`~N3 z`cwJmY<8di#sI&*bHcflG=rpxkmmYQTQlw~drp1N{(!6UEyegMFVcVLYivnBOZvo1 zv#P$7p8aj%e9R`D%`w+N5x? zSMga%TpGxQ`?&jE9_#_qPWZCrds@b!{+(*BSY<4A<4NQB8-F`0ZY?g3obddIomX&e zrLU_xBe?({*`q66Kzt;vuO2bvuFvlU1Hj7bM`{}ciS=kBbk<8S%b$bWTcZnO_t$I90E^J?AtdtCZ%srXy*NA}qF_u)_b zv+B>*{EPFQYh{@u-_d2T&Pe{__~ZNR`xo$s16fsD+Mmfa$=2@$NCAJ=1r&Y*u9{6o z=wMd;#2Qa&u8aFKo8jnqT4YnwnpNvoe|h}LV)okKjX!yo{rpGphtIK}|15srxmo4d zroKNf)75G(w0?wtofI<&p&nUhb+G@6e3OjLi=Sz@z1t1kE~^s zw`pbZ>PCOjfz)G~G?|O-x9bZ2;ST%bsQ!JdXP0Kxi>)yg)|JicqRNwtK}kGq_`5>( z^Y6#saJl{XC-Bd_&c1(9;<>{9ysUXY=l?71`~CO>UH0qSgTLx3`~Fej6*58C(l<8QvhzJCIL)ral-7xB-3lJG z_Vf4Sul>0F_UysG^a=a^QT&si%&G^i>!H54e29C?@7F`~kmY-i=J5A^I;-5rn7^`< z`;ksn*!MX<%zXSzR-I|}8|U|56eqAA|Kk6g{dhX@*M8o|&l*7o7LW&aW9;EqGQNA-zf@Yiwmf?w*8A)j7icAk|sQ1KaJEQL7LWYGPcXg*}9&UwfcFw z9*6YxY#RU6WLAB58^2Hcd1~+Ct@di*#-jhb_WDHPY8U_avg+(@%K2Zh6U9G(zw`U{ z+E@IO_??AM_Wzh}n2e;j|}Zu|ZP{Gp$o z!1dq9xPN(%eSZsn-_Px@lim2;zp$VG2!6+X_WiT?({uLQCyzh+EBo;`ew_B4&#ISf zGp7GioYpc%JMf3^x9=asA6c*$e-rsn;rIMLs~%svUt_tx*S|eg`EEUxd1q+R9WYs&Y^|0*ky= z+ka*I&Sm_uzuS+eE=K?TW83`8_C2lmegDd;S6SQDIFCM3cSt~=Q~K}+|81|orQgQ! zr~hN$zkolwvVDEOqWjsezbt!=7K-bq0U`$vJgS1ushXKJ`9etBq=O8*w+W&_;RE#2%4T*B&*Ee*d=e z`8vq~Jw6&g&HXP|Zkzjq<=5FA_#>y-_YdOtoMzuYg+Jlm&R>3AwTwS>`u2S`tLMXh zUEjJ<#%#^@e(U|jR{V~~+K;CXf9!F&ZTIPa)bZ@n@r>h-JU+MG{npZYCu3p(fAEP$ z{?_|0l|L!g_Vs$BCu!WDVQf4pXZihB%Q>ZZQ@XH*9ne2_Dd!T|3gUizQ@9)Cj*|cq)%g&|4 z_?upA&#%`%{Lbdw(aybF>z{Eh%9Fv zUa6D#UGjvfr0FEhz$((@-_vH%f?4`vtayckzhDlSiE2lnfq}eae;xMJ8oP#<| zKwm=2Je(y>bWcvr8fo_GABZlL)2-9&;}cn1*LKd&b3bd}_UD17VmcXHP58S4_Whms z#}4LfUkeW5pJ~mh&WG2d^nSw*9Z%ss+G+e%=j7C#5BJmeg?b;-em26EYuY^NV&^_Q zE^8ZaUZYC;HhzKg&G|X?(QV2pEOY)+Ig+lEbPX5g)bzvCmFiP?_naRr)t^+NjY&5m z<+bP3gf*Qp)?U6@uS>T2bB=U@OLDgFZ8*Qkczca~e-r+t%j};QJMmAxcKddIl&;4C z{rjXt_~&1jQ+{i`%g$Sy@3YA-w-(mB8Pcp=k+XddDf?8(`FAD~tIZ?or@HLg$mz4q5%X`?u48n4T#H(2Xn%!y~|u~fLm zO5yMBv*(wX++XH8>&BeA!J2<*tuF3mUZ@KwofG_|$wch0?=rW0Nz;Egr*1uJIfXUr zNyTC+&MVAqNf#&G!p%AL#ck3R!Yg0vmPpq&V1Lfmeud{(-fMp>2Jo-ky6yE!>Dp2H zu^0cu5&Q9s;qSlQzJDHn+xzVAPpabVV?SWu-;BTMgZA5}3%~OYdwWr7pJDu?!@2T% zO~yFiyw~K#KZAebBPY23Ax&b$ej16(`&ITCAGcrc5dM*v{dSGwAN-X4{1f;)M(vM_ zH2%iV+8?K$ukrlb=j``e5dYxk?fWD68^35jp18y_Zd}t@#)bLbP743*m+kxAUuXS` z+v_hGcRu_hUpuie)I*xF3HyCKiof@p+xkn_y)s7U@CPPyme0z_b8MD-A#9#2^iQr9 z_8ZO#_OstPvFod5(hN=6@8>T3P5)=VpNH|UeBZu*2LHql?E5qLd+yw}P0Oyw8ot5$ z_#^xEZO6ZOm;HDK@Q=>q)VEHe12^wqS@(Pu`cc->=aDAyH~nNg{~mc(vGh#I68`C* z+KWfVb?rAf-`w-?J!|1ws<;m@?m4!SCiZjtZQ6%F{0sa3as0mf?E4q+yMJkaZLIzl z_dkAR&o6D%fH)J`%#{%F6g1Ni-m_WaUzz4&V$Jh6S|IB7ylCzd8fn(@Ec?<0w= z=G*KimhGpJ_6m?@@t^kUFaBQq6KQ*XiD?Xf|9?&_rUlZ(R_v7_F;#zu@%fNFzaIbi zeVG%BDNLGofkDSGio)31_t|HEcWJ(M z`wMHm|GeupYr$?ldCs%<*=L`9PEKx~hMCo&w%=P4pKO#_ylPXW#K#(+Setz7q@LW& z>QHS8O8jHZGK*Jjib(wA<|o#s*JYWK7Mazd+Vri&7q!eRUbU&?dbxhp>crYqB+Cpq zCG$E;B>v%`x<5KYtspdZ|BRrO-@|q{c}&OO}%89f?k=|F;U|4&&#|H)mNpm%#{U|8NZ(4 z&VRB@@WRacO4U&*@zIM;tPbA>IiL7VW_7sppTvLZ&wQVYNc_E*WY)JTe_Z148jyK> zO5!IE$~@ltPdQ!}X1)&vB);pVndc8ne7j+p#j9-=llZ2WS5~jfk~`*k5?!~R=$_6F z%AFxkfYdUd$}**a6I-VBMw$1osLc2}sM;38WSP*&%+^K4-zD+EQJKd-FY$rVna95` z@%}NH$2WaljwfR?kH1Lbz2h>EpDgj7@tMazCh^XM%;VpZ_|(MA<0~aTd3|Q_Y8-Xl zB*%wIl_&K2cu_Po!Gfa zku0-oO6AYJ-zV*Qc!|V6ct>XOYFm^`{FT#AZ0`P2mI+SJe7)Okmi=u;=IgE66_8~n z&pfet<8E1|d{*Xj>}rWGnVtE1d@Aw9b29HMwH~c2&g zbC)a=elYW8)Oy5ZndcwMtX(QTDe;e$Wf1)z|b)(wXzSRo^Ke{jS)?7=izg5P^^32cadP#i#)0xGqHcgcH zNoAE8zh_2mw^CW=^=B(HdYxC*Q6cg5S5}_Tdu%FYnaHZl=PFf??=9)y7b-J;Uyi%} zvP{wH%-g1}cb3XBpT1mq!tdot$Z~hB$$UR|w#oVD+RWlryL=MAZe8UGzt?7xEO+H= zKb;$E&vWLzHeJ>FOZ?OgneT@c5)SG4ZT@K;@|r-+ z`*>=v!!;5g-BOwH`*7}+_|(?S;?=fTC-MHbD^K|SNZ-nGWr@lYe(yoo?NaH^$`gJc z(?nUWba&+my=OtK-(#|zXK&>Rz5hw=Z@W}||H}NFQ!SH|kn#3@=F6xtdXX#>_^>kL z_Z&@D@yW`J-*fbs#791+S0}dJzLjN4zpnh*_g&Zid}POWJMA>l~_Po;UBkQQKpH#OF88JbtRg z7vyFh|D?nhHp@JIm&6w}&pf{VZaF_|k$HSCi7#$hmCmSDp{{$UHtE@fY>{`S{wO8462$)83i4KPK_- zpGW&@t@nSa&jpydNsZT}#D7^(mGSEcYMGPYllQY;Sm!pdzE)c(%beV|>gTR^rG37w zSmJm6M#f(~snA@<{+aLADwFsD{>ceqiR? zda}ev23KYL`r~5~?;DbN{96*Axb$bYLE1IPO4XiWndk4iPug>N=JD4^d}4U!?YURt zBO`vkKh1f7+Q#c7K6u56&G#u;rt8Sej$LX@c;A=v*HM}8p8<*gvMBTXVTphL>dg97 zt#eG`@4Dv1#`UMNOv&#uYm>YGe;~*A;EB~yD9hY^ZRYDyEb$#DR;9ldZ00fj+4cJF zWzS1;-Cv+!zC`zM{)dU!sR=QqF4tPa&Czr$n*8f%a;g=lpb(b)cx%nUsA@$omJI&4Nb1W>ACvV$`#4ZN=PyxN>-Sw z`sgmn<+L;mSM{bO|21=~s@uxnQg684Rl1!ypU8Nv!@c5@>+k~cspM)K1jO$WZ=mwa zP1!`TVzI`qXe-9@jcVDwVj+r)imCc*=UXi{Nvv6|Wh=x~`!(pH3!N#ACP;4{N3OETYV@@wXsN+ zn<~pasFsr!+?zFA&7nio96C-fDU*tPPPAXGI})nud{CC%E6d7Mjyh%i=420$9cx-m zMMHCCFGHmgc9HyhOO^}YQ&oK~`u*N4Y3saf>D)Qe@4l>3nW<$amqxquS=Qt{@1^GM zFj>CoXR`dls_HfW_gpbJFJ3Quc>~$|e!82fpJSBwlFB%fe5nVjs&~oXst@i{^Df4L zOo$i^j4N03p4xUL;&+R8 zRNKzYEA5+;EuE7Umu)WJJS_vQFjoy7GtjOvtm>;!^*xlHSB=A@s!x2D{9S8bkzSjp z_C>X?#Ixlmp3--gfw_JOZ}){fzma8qHT$uw>!R#|YCm?jpA4!w4F}{!>t($oTTrO> zete00%m_-3=O3=BK1tOtH?`hV#mdFpK9F^tlO>a|xY|w;d2F3Lwn6^Z`;83Id1~yt z=N?&eYoBe%NGp`E*JYWaQW+1~dYQ#)U#oq5mv+vRcDl#+@EKW0>O5!+%pD+$dk)Ba z`)F15rYxQ7K6UQ%lXV`PmujefHsL3em;73vv_<`sJl`&>s(!7e@1#w2%z@FYtT{g& z{r%*-;@#1o-l39Ljg{4syXbM5tC0I%H51IsmOW)o{b6~DhFSY+chcZoIqVKHL%WwO z|E(-v5S8f=NM^~-~`PVwB|#8G3c>p?j_JyBKdm%kkuHw}vOa?~~)AYLl| zJL9#sXHXt{Umm-srY^UwIG0Q|$iBH=t*T_-k{l1pG6l=4s>`v=;w<+Z>r!>THc*|f zx!V)xYvG33f0X9damMDhTaBqtC12T-RrsDvX2!I#Lb1DRS-#jcViB?5%HMjQmUVaM@tb5fmVqz3v}&a4+Y*U= zU1HBuv19W*Lor`O#lIw1^_7dC{4~y=+*B;!xE&+&6gITF{)?pycvaX*{omwb0iz5~xx>E9Nl%sY+~+OF`*~=s=NjSctUz8#_OQH2_6RBa z5K!%F`js62pRKCyjP^~GcBtc5NAXhe^Wl@^csn3>kUXmA z^8xaBxjf!iJ?_ru(vK3MmJP~d@5^I@YaUZa>mj*n##hJFusq)MxvHAq0iKhUb)^K1 z$?Y%y56o4&f%|n2{>G*6zF$i8>E zoI?$jXz69y`D6sD*!usG^?1IjTJ^aeJ8rD(H}b{QCRWY~hRMS|iM>W*pT#y=r1mkK zd&&+oN9Lwb_USU<^JO^IoO}+H-wR0>_2X1(E)Pq-bt|i?$Emtyy>Koo3(r&g?3@Od z=lO=@6%^+Ae`gX%+vWUC!c}|Xvi$q9{4H31u{sx)pK(i{D~GCM-{pD!A$f&`d4X|e zwv<|GrU6;LQ2u$omgkF-&;4CEY0qMH?k-<*QS)56_P(7|=c9Ezy_J(-HJ+}KT=BnE zReR-cHQvWat5ut)idTrc-!FZ9{y6@8y!N=3`QsWje@MO3zqOx{yWhW{+WflYD~nZC z-=Dsp9M5;W%>(55y_^QukxkAef|ARVlJ$R~s`@WD{x9Zh1?jootTUHaMB|`OTr)*H^MXkd;-Zc(RGr4{f-UkPK}3iQ+u@fz`Lv+_GAh&wu&& zdFhp66=EI5vd{-O*FJu(CgOU}4)4xaVfn$_-~JKFSsZo`wZT$wC4vyL`M zt8qTyp5G0T?fY1rO_2Rp9oJK8eb!Xz-@}$M`FojW)I}>b7d8E_jMsluRWFdg^*kW$ zQ|I8JtQqEDp!&a;#O1H8s&1m<Bvb zY-!EE zRn_WLTHVz8b`&cjD-gSqY?xR;Os$r>sbzy=!zga5SYfR=)h8ul1H^8u=@a)@C+CSc z=NqVwb^Y}@d@w6oXP?N7pmOb!T$5hIYoYEO=H_zy*FAr9bJdKO`E}-!@uIe?_ggvs zY^bUpD}U?XhIOCs$$U6YhQvlWjJ)B_#cIAACb2<@bb%YNWLGI{J*YB|ZLoeoH(i-axtBRQr=+ z&xE9`rV?z*mJBpWytG20|s;fZk z$y%nCA0`$R!?iVc{na{8#PWZZzt!)B*WT|hlO1+Ot!Dr}33*WBTW_qYo}}WB?@R9U z8`(8R);?QT2Yj`j6|&5gvP@7dopdObhGr@L|ztBxV% zlH=sptMu;zx_uZ@r(WGYNgp8b`c}Gy8&1{@g3B>Kmo}r(>wNcK67x?v9J5Mo+sp?V%nE$YYb`u~XDz?zqx(n(DL3@>ob7`;B_c-6qG6{~~mroxLE|AAxm)O>utEvwnw)Q!+dyPwGfvo4~5|XFfbBn z@qBsQ9k0^ABgIwUOcwWvx2jq0GPmDUKbOd3z2q^oos>O>_{J(;ZtnIM>nd@b6iDuC zQD=qt0C821*$-5FQhZn)epLKQar0bn9P)U6kamiPSsHzdb_GR zT(i7fGsN=lwJ(_iWcf?#T3*dJIX}wymgN_!<=uGz=l))G_AWb`oF)yE%6iGey(D(8 z#12xiGUn!~YsBh#f>$nG;PU(>xtG@bx$zA>CML^rDOoN@EhjfMPQqeeiv7wJ8Cz05 zD0WnAGWv5-mgJIONmSPaPuWj=UE#$SE8%;F z8=}tD3w6H8_-wy=&LP{6p1-@lTdDHhE&0mct*S1lS>F+AePs@)d5yxKCDXBbep<7> z19P*k6_O_iHKeYks_nB&@|Etvaa`Jm=hyD>R%QqFVv5^gvY>vVt=38YHFfGaG0Sr5 zDxzL)pj;R|zMPsH2gq_2vRrrhTm9Cqc|Ait7L>={m&bhSvC+pLYbST_lE+f=*i`kH zd#ud5T&5@1X;71PLOL_YFzD=Wp~N4 zwT~xXiY3ULdi9*Q$W9ikAj=nfU99%=!i&V>VrsLgo2oY;wyu_`eQTmvx!AcX$o&mf zv%g$ncCG$uAMwh=5s6jpG0(y0$hCj3n!-H#>Yl1&wZs&>cYNJW7ON2JC8p|A$3OLI zp!z#0K1^KAzp4-Mny4h5Q~$`#F=eXAmOEApQ^Uh z>=QB{VxN$GR$UhiWw)0dD{ZIHuet4#Wsm;r__k8{4v2j#=Dxn@jwP8xF{ilKEeGd? zv$DRV!KAOPcg&Xd?ZffY-F`CK-1+WyvmQg$_A8Wn0utNq{i^Cu4E<*_dv>hq9f0p7V5QVc%LrA@Weg(eh~M0UA=%rKs_0k>kBA7BlLZB9<6Rcx*x&) zXvAEh({Y*p-|Ul|w9vQIlUBK%Y4HM!54HFNi%+%qe2Xu$_$rHUu=qO`|J>roEM9+q z-Tl_q;yo;WvBfX5_;`y?wfHQHFSdB8#mg-Ivc)%9{2hxYEq=`6O+Kx=|Jzu+qs4nz zyuZaqS^Or8&$f7}#b2=a>lS~{;s-76d}ir4i+8d30E>^a_%w?zvG`*aUt#ez7T;>| zq{aVh@vP75?zfXI-o@g5Ek4HLQ!IXu#s6yY)fV4w@y{$?W${zLsJnj(EPjQ>Ll&QH z@nsedwQZ~qWcc44*CH1Ghb7NWi^nW^_FDWumOS+i)ZK4RySm$-Z}B#kJY6mBw|Kyk ze~cym7K{JM;>DKy%PjG)SbV3&4_dt5!Mgi7YH3e}#Sd8A)4uNQ<+J!{miG88-rwS5 zEgrDcJIxXwvG{Wqk6V0?#fvTNiCR2v@oy~o4_M+`{JZY$e!9iGSUl&ny0>qkB|d2J zKUjQ$#iJH~$>Lir{-MPWTRiJf-Tij5#eZ$_3oU-B#V1&NipB4?_#+lCv-o<8ziILJ zEq>7AKUh5H%ewpdREzht_+X1)Yw?*De~3KVTpvJJSv+p>ofbc2@rGZe_k*eTOpEul z_#lgqv-m9*|C7ZZu=omcbH1_0;@d6$iN%jtJnuj0{buUzL~i=?Hx>_Ad_4KH7!T7e zzS!bXi@$8~Z5IF7;@?@k>DTG~W7^-Id^Ogqz~Un z*5V;co?VvsuPmOEO7Az*&QeSMa*LmC$j>R9bxaair z`IyC<9!}qmCcd4;&$aj^7QfQs zH(C5{i!ZZyxy5%`{J$1&^544qp`*nwu=q%ePqFwt7N11p#B7%`OMHnXev>7Bx5dA( zc%{W#ep7e9on!Gq79Vf%J1zd8#aCE-t;OH5_@@^C!Qv->TX#QnxA;Jdmsz%BkmCKP z>;3u;i!ZkLGK;@LZpPa#izhAZbiC>Fp~;hP@xzw<0ZV+y;-wakTRiVb`t~yY*~#Ju zEP47-e0~SLT|8%`_k;1VmiPioe6b~d5xK{Gqqw~OLwSssSo~>Ap18%!EqRg_&p9)_ zA544lE&jfx-eVST`d#{VH2FJPyqCpCSo{`?&$akt7GGoWcP;*v#hZLz_xg6S_(c{U zW$}PzyWDPxFR}Q;7LQqclg0O0JY;F-QA>P_qjmRNH;Z3l@lh7P!QyvWe2K;XYVkD| z-)-?j7H@d0?tW-%@!l2>So~UxPbEJU^VK4YM`-(+_;QOU$vYx{PKWgQXD|FYs@M3d z7B8SYCV$Z4B^G~|@|!&WCO`UXeH{7G;$@WI#3wACvUt1yrH==br?%L z@oCx_uz0b>qZUtCTz)`6PiXkx)a$c&k;O|aUT*P}#q&F-x93;bFHWgSABV=fTYQ+s zCs;gSskhkTQH#&A*+etsB_nYwqwcmIbOMHKekGFWrlHc>| z^zCcvEv7uZ(EfmKGmsrVB;rSyt~COwm3iUF!`^v#7`jq7V9VIX(tbL+-aPp*RK^y^O@b#aK9Hy^)*uJHW2s6p){ZSy&3oK zL8;$m?l_O*emP3>S=Md1pO5}+sM9+^+z+E{1TV+^FR+|BYVL(Q*?K+BM0^|EKgX2t zVYq)3%T0t&g8R2Y^;trsE8=li$$N5lhhj+p9m!M3*e~bG!qTB&5!t%u^-+|9Y+@DeIgs;Z^%_w)l z_u>8#l)K?4HPG!WNB(!==fL;E_rMF`j!!SQ7k(|=2Y(M9!u<%!f5HEV`1?`rgI6H# z0800JE#$FPi1YN&^?rc(x8MQzhwx(DFGcwgd@t@FMVW+q&((PgQGN^$<9-z7C-8%) z_W-;rcF0P&89&|NEgS0mol%|x_x03Xgt9xl823w2y5Hd}br)dyI6Q**qqsi;k1v7W zi2JR3>3ZDnSd=`o-~srzh<^b79Lge0DF4L$T`1k(ua`WZ;eHA7tVetmyd3^2ylo@h zjwJjwcusHaK9q5I5$=akZh)VQJW=@T@Bq95?tTxnv~x1N1mzouUxxcDQErC6iTloZ zx?L6UeB2M9+yal_emTlF;YsAFM7b5-B1h*viukwS-Qa~Nx53BYzW02++}rSC_(GK1 z;mdLVA1D*>A}k+9xdXl%ab<8X4j5m<8#LDSo&j%<`#n*f2_K646Hs=5{}J~S$lnp} zDbRTfP@V;U4DliO+3+>+IJ^@)0ry;>^X0=2!2|Hl@F?zAp!_wwaTC3~_d;EEfu9X8 zf_H^q3@?Y@4bSPLm-nHZ3!jAh5tQ@bW00o;KHucI2 zTM+l3MZEjnW#T39GL+`~i#8!Hi4t#1aw>7Zbu(R$`feO`I~Vs8$m4$RnD|h*bBT^W z)s1$X8{zqIyp7434-dlC_v@(Ja(F2mZ;x;`z~4vdb))5bmvDbC;yb`w<>_{K`s=bI zJb?SfD9?iTK>QGtxD4UM5SKvN74Z`gmoq@;?*^X@FM#)ePs07j@pwOY1Rg_q34AT$ z4#4}v_re1Mb-e@N-@(h_1L3U)={P^iS@0IkwMXFQb(qfZekc)MQ-tT}GkiZjhZ@5D zAnpfoKV_?VV?Im*s;v>9jCrdXg+OwH=gk1q8_t9nEeavAg$xyrkBLw)`?+7%zkQ~ zKbie4geNe@i_viNeCp^BU4!0g^8H|_@HCVr?uTlZ8>|4XHRtLf47gy)6z{Xrzdp z+}p2Eo{jQ6l!H)Sh4OloccPq!@*$K@qkI|V8z|pJ`8mq(P&WJ%>PLAt%JWbTLU|R+ z>rvi`avsWuP(F?FWt4BAd>7^CD8ED5a1QE6c{a-PP!2+Q70T;T-idM^%7;)sjq+ub zZ=ie^<>x5BL)q|d)Q|FPl;@!wgz_qs*Q2}>@ z@@$mnp&W$rDwNlwyc6X-ln3)Q|FPl;@!wgz_qs*Q2}> zPLAt%JWbTLU|R+>rvi`avsWuP(F?FWt4BAd>7^CD8ED5Z~^K^c{a-P zP!2+=qkj4Smmc_~2Y%^+UwYt|9{8mPe(8Z49H*;Q#MEkdv(YI@Z79 z|BBx|yX##Sm0o(=<5vw?cj2pF-=eN*=E%RR@7H__c=cAxxRCI_W$+06cH~o+2~@o2ejWdJ_y%}lsrJ9%Lh%pq&|kGb zs2VP}-cq`Gp49iBhu;d1Kc#(816{5VujjOIywlW{k{jap!&7%^H`k}z%Z60(4&u%0 zIHSZ>yh+V4fd|N!!h_`7 z;UV(IvV!h*A@2{5kk5oi$zOrT$PdHgTE<`9XM?yn`G#RJ{@MvG6GQ!|)jS z4tShA_avP^K|T_mVe~7o1goA9+8xpL_;9K>iXuNd7fEMBZ6muvYbk$tS=gDGbuFeC4#d4rjH~7us?snX% z;Zm8`)a@B~{D0a%(d>Ne<~hyT^_3j3TAL{sKHoeh3~TKdU{q7x_4Ng8UJ9l6)sTMcz!_ z7@^k7Q77{5p7?{Bd}K{5^P*yyaOse~SDvxbs(gg$Kx2z=P!b;UV(&opk;%`6zgVdHWVM?jf(&P3QNL z_ksJ!?|}QsSHT112jM~T4(I54L*!%OVe*IJ5%L}ID0yyooj*oC5FRI=1y7K#fhWno zfv3p3`E2ltV;>Y?-d$%n%Oh<3nBM=5dg=Tg@_ukH`3$&^{3W=b{A+lC zymN0|Z;*TfJVd?>9wy%dkC3-GPv?)44~56b=fdOUufY@K$KXlwp6Ba&Q{*?poqwnI z|1)q8`NwcCdD{Y=-$y7Hwio97Lo!>c>-v5K)9`f06FZo)ykNgPSPu~3^U2lN=MtG2XIXp!E z0X$58N?)BnLOuc>C0_`Sk#B;>$*bWB@(X^W>rIkRfv3nTPcN(PY^^lK;d&wV#`^b00 z{p8ID>-+)oA@Csi9C(O)Jv>bQJv>6*qfplyCBF$CBYz4WC;teZAa657=TDMf0Z)-H zf;(TQ_y1?Hk&lIk$sdMC$alb_&MguKNSI)9XWC_F|!7ak{n4W1xB z22Yaryi(VjBEK2#{5QS-pMiVGKZbkB+m6)vedHtIe)7fe0QnYpkUaY;oj*k07ak^` z29J=x0FRO%g2%|u8l~%vlaGTZ$RB|x$#=q2Uob}3nSo4& zrwO`V5BYewm;6z~k85_mN1i0TPTk`={W^FE@gBtAFw+TPm$jNKR~_;o+LjA-%H+MlCC#FJ{Dd<{xCdF zz5`xPo_nLtA0rYH{5E(2`Ezg|`DgHa^3!kD`Mu<$;jPIZfP2Wd!E?wP z-JHOv7 zL*X&}d|KJv4s>U#6Z$HBeikHA}#?}U5Eo86)F=a3JEJLI$B zM~|kD|F!TG`4RX5^6q!)dXwZg!uOIdhbPECfLD;8GEL`?laGLxlP`qF$Tz{u$gAN| z@(YS}y`|(+;1Tkb@DlPb;9>GJrtAF03Nd_O!v-u{m|e+Bs{c$|C*yqtV1JVxGNmd;;Bela{sJ{?|4{vtd= z{uR80yi-`$8zvtQFD8E!9wOfjpG4k#w$2|U9|A8Tp92q&uZI_se-HPQ_xO{pw}AX6 zxR3lPcs}_@a4&hAIXZu9@+;sT@6%3 zUh)oeb-fAlvG5A=hv9MZ9q@AU+<7{GjC>%xjC>Y6O1=hOO8yNzLf&n@uD68z26&kK z33xI2K6r?{Rf*0&iF`ObNWK7GM7|LoAg_WKlApgo*Xt+04PHS09Nb6#89bl-^m}xE zFZpPAYw`!+9`bGQ9P&mBb$*AuAN=T%CXTx0xc@uO40wwCCHMjIui;7Z&iCqi_mWS5 zC&-tHOv7L*X& z$w$D;$rr+7GJ9@6=X$;ZG$j{DKK_4zr^tK%S=W1j{8o69d@O_mX$`tFAXeJ{Dd<{xCdFz5`xPp1WMu81D+y(34Vb5 zYj~2p^9o(>Uh)a>1o<*}1^FI$oV>-eI)6F&PH$M z67px@Ve*gR#pG?D*ZD)_BjJ3Z|Y$HBeikHA}#?}U5Eo2}CMbI1q79rD@mqp2p2y5+e4JI-2oiu?%t0D1SAt~W`3 zBYZFUa(IIL19%1bDKF^!aq-Yw|1L z9`Z%-9P-U@hrHgaI{#6;Kg!Jiec&nbJKzV%SHY9y2jP3kJG`drO^}a;SCBspkCX3! zmy_qlb^aLnKzJGXEO?ZB4ZM{68+e4g+Xh{43Hc51F!>YkV)A|P5P7SA>im<)hr@&9 z3*bfM8{q-+DtICJ`5Se;e)8Mk1?11cedM3P^T|(tUFC1#MCg5Vqv2ul2jD^SZE!EX zm&WhtTcb@Xe>=yI?;)%Y?$2RGkaSVG@hXuh9tDgAzs z`98zPDBgT;;d1hqq+Un*VKw>J@HlzrH+1|C@(J+Yd}Q`$y#mYNM<{*|yvaY)<6CUj z`OhRD3hzxm7e18yHTXF4WAG$>e`C)IUGJ?Fe=~dm`7`jx$Ula!B5%7z=YNxYBz!;l zV)$_C&n@t_8*_9Ao5&BrXON$@RoAs5?@<-s$ zlJA7CA#e7U&cB&_Fnl-pZ1_R)weTwPBk)$Qr}t;~ZMxnbI(e-{vejWUOMx zjjK`c0~Egm{xZdHg>N8l@UG6kgZyInKJw{s|K{}V@*@0l@~_}2%F}6&uJ;cVKOWvB znV$bq_!AVr8~ze`^SwI%R`Mb6Psr!M8&#y&yB>ZK`SQ>Gf8@H&XohNnP(yioXp$ zmHavQ0`kw`e;Q2e!+p`jWKKU2$-;$s4mCiqld<;B7z7)QOd^`L@^2Yzs z`8!j8_J^PJPI~)i!uycF0-r>F7`~Le%h$Tzm&vb#A0&Sq-fm}ld)|W&C2yJ1`TszE z89YKhAO1A?2KajNAK*L4dmq;IeolTX{3!Vf_!+y>*K0rg0@^O^|Eu#4qWDqp;p9u; zza!rYA58fhe53P^A-@>@2IZL!f1mtC`1j;r!B5(qzP_El)%Bi3J|133{wRC``EK|h z$(tY1`5z%40{5KUSOwJF=D>aA>*23Zp6}uBkoWjbrEK8@D9=ss5cyN^2>D0wa`HCc z>-;II_X>EkchmcM5!_F{89tS^d%dGNe@?6P{^6qB+os2_f={D*55n&y?{G}l z`xyCH_;chB!|$i|?|_$3J9Gc1^T)^s!V{E#7JM(ouYr4hmA<~;zzfK`{h;gpnDXBM z|B?I&c#}Qp+i@TK4)Ru&I{&@o!{JYoFMz*9z7hU9c@=y)_4D~vy54f~+u%FNpM#&h zH+{W6gZCjn{YRbuD)Q0r+sGe)&m-Rke}cSGwa&keydQi!`3(30@|WQC-b-Jvui>YX zca~Q*)cjCDJ^?K4Q<7y8)Ox~iN&VN1S84CX+`CRy;ZpKg1l#zuJ&`hC2Uh^1<*5^4ai16-g3*r6AH^HwWuZCYoenDeh?-ueY z@XyFs!aX0PxAO~lU-C1W=={;v>HEtVxQ~vrOW~n5>G9j)5%R{lI{%YYZ-01<;%CD5 zlD`6XPED`(Fg%~UOH*C%f9SYy9XvqskHg=kcD@Jymb_&%o&Tf{)BE`{cqj7t@C(T| zz%L{J0Y09*cb=|yI{B^e`^i_p%gFb`UnOtfT<70MJ_`Od`4YH?uD5T6pGDrlqw|+i z|6dF*C!Y>alD`Q5ctd*se+93i*JnGm(DhzUuP2U&-$}2pJqlkyz8n56dGnKW{=M}2 z$PoDd$mhUw=yieh@V4aN!}G~|wAA&ECBF%NFZomO|B`eqoA4+}&ybWDH zUj*+>z8OA}yk0Ave>!;|c!c~8c$9n<{3Y^(@Hfdj{7TpRDfw9Vx8x7Q8^4)8-gdxG zBhNiW=Pw{12rnd`1s_Yk27WvFH}HGOyS3K!E+@YM{u=oc@OQ}f!T(L(s*TQnlzccm zm#_cBPb1$5A4pyWznT2}Q+2(MklzM>gZw#olKeCHf5=a7tMj*@>-nSMXOlkwKaYGH zd@OmRb~^t-@_z9DknBrH!E6Df5 zQ{?Tt>in(g`PV3TA^8&c25Qe%c!l#0AimUZa1oVoU7T52gJw^T-xMzfp?}zpji`SL^LBt0Ue-7f`w#0vq z_z2?LA>Mm#-T4QKtNw{bbpMof(+kY9#Q)iim)GmjKdrNL{4V4PzN+&?nrhGMS$DnN z#MSl+tV`csH^6;x)1ObkJ@A%TuYK^;TAjyut6n;P9DWw!hr=Uq85!ZkJVE{(JO)1nc|LVVR(D^40w?IC3qpcIpV*D`{1U3Iv41AJ#e!fC%{wX z>DzZ1JOMY`aSuF3-r@qCKLT%o_6&uG;HIDF!UGil8r%mr?K}qekoUY$*PD7px4$jw zy&0Z>o9+G#JO(%W@5k^6+-&!@eRTd1`AE1QZpQOsxEFpZ+Oq}jz)k;TU!?ONcv-jK zY{$Ovz3@EbnFg;Qe*qqYn|?k7kHAfTp4AuYMLrH5fSYzc0{6kqdhLXJ;AY%5`wh0s zOX=HXFgyV_`}J&i3~u^=Ej$7@*dq%)xa5D}U!XxCH;32qK-)eXOZr1C9e!5;C+_ZlR+<}|9;NebiD^&(Csn%*LCm&+~j#29)p|l{~kO-@hu0U-{59EUj`4r zP5bA=z3?;94;$dgm~Q8<;XlA*aI?L757PBU;ATAB3J+4A6>u*+ANlvgQ>%2nX8&zJ zSm#f`&2eNDJO(%GwFDl9n{l!g?uU0my$uR=elOh2&lkfTxT$wKJoz`>PP4sUgeTx; z{CowEQGBN%=r_2jcRV~q@sGj-aI<}P!+mhGzcjy8=Xc=dd~ygpzA}CL&Vh&F=c9kt z!~Jk`e)T=v13w+{J%;LfQ_t)CX1=-!o`mNj{wa6@ZuX0h;BmO=&o;w!{uucc@KU&W zHEXWjcQlZnjGwcp=5#0r$bpe!2?oft%yqL3rvp-9KjBcDP*E zn}C}n9q=&a&mE5Sf}3$N5bmcuv*2F18UJhG4&3z5H}K@Mdc92k zZUJ3y1>9`k8{jdxS>Gq%5x7~eeee+6jJH-JbbdcPAKQ00+ygiB$pUzCg>I)g@7)Mb zz|DB8f|tWh`_KQat~Uxd?Ys>hhMVpC96Sg&{qq^z4>!k))34C^y>PR?kA^#NGY>xi zPnPNRGV{_lcpPrd#~NLU_QOq{e(*5d%;z)ULAcrOFTwqA)BdmFUbq+ArSnK#Z|WJ{ zPP1Jmz!Pw@eV4)GaI;u!SERQYRk&D!A(1lzyolz9lKwn>-ECT`S6YKe*lk?pE6eG50j682jQk47Q+2-GyiOYd*NpLR>K{**&i?Xovt_eSKWTIUr&L@ z;HLd6;Ssp0_X~IsZu;Sjap*U=nV-kNlTYY+&3XG$cnof~%XWAKZjN7#gF1f*Zu+M` zJODTCp9%NE&H3aja0hPs;V?WNP2av<#_M_`a8vJf@DSY8`#9VWH}$>;_rlG1Zh5WF z@4(IaUItG+uG?eg&-w5K+{^{8dV_HDJmOZkA8z_*1>8%%AD;S) zZl`Ha`-wV#0&d3PD0mcZ`ez9|1ULP;6&`?_^=fdP&hI0?81BH$_?!-pFVpQb&m&%h zN8zTOU%|t0vtFI9N58>Md&a~4U|ELdPuj&^xJ3f1o`P9oj(RQ+kG@V3^&))9)Jhnrk}UL zy>PSNHM&jbPcGH%G2Razg`53o20RGwg8k?vxEF5DbG|nDA572R`F5-q-1N@`co=Tx z+huS+`5w3fH}$rdqVvZe(Dn92JBPx<@E-8F@BrNGcdx;HX4b$|Ny zy&0Y$e+C{S{}>*DoAdm(cj){fxY^%F!UL3NG2BPK1@3{Hagu!}+P_4%-^{~(;R(1o zj!c8c;HI4~z$0+eKZoEUxM}}c({#N7%0CY7g`4sD2s{-4&3zfjqv1R-F~xQFNeqBrvE>HN8x7v zIpq&Je;97&y%F#L+^p|HcmdozuiOOBhnwqi)o>5ooL65kL)V+SFMXU(f$xQz>r^Y@ zak%NvFW^zQY3CVtVZF%5z=LqppG)C>xEWX5;a<3z&l}Iw`E%f=o&DjdMS8u=c%BJQ zz|D4l1s;Q&dJn^+aMPb%{;2DXz)ii^!NcT_!$WX$-u@mu05{{dfXH{*E-JOVfM&Vh&EW}aCO55P_Td=K}*&3@5i zuCCVuH{Zl~FfW8n$7+3pX+ zV{nsy2Rs5d+c9^6&L1Kl2=~Ly`Q$9P12@}c4LmU~eH?xRkHJkpbh}5_8zH{|9)_EK zegZxTZjMv?-~qT9SFIN6{66yGa0hPA9~Quqb9MVoo{i#ly)SD&;=}87yqU+^->d5l z!p;6QO1!Q-cOX8wUgt6KtKf0C*99!G8-|Pv>{wrrv?@B-YoAw^{Hw+|;`U9)+7c--y@MKPN5L^?KsEe@y%^c!Q0EDg4}~Wy_4^g| zR&$)W@V(@(!2{&S;6C!6OLe^-@|)o)YR@z91o_AC7@ z_mXEntn){a-{k2FkCRV>m(lvZ0IwiF1P@ZZXO-%D;}ky*9wUDQUQWIfUP|8V5%e4R zV0a1nYt{%2ipG5L+~5czWW0rC%AZ|4l4@0~v7QJp_Y+hv67Eu8!f zdW4(zKP-f|Cf@|lA+Ls~Fb|mH)CG^}{WrKk@3-c-GeunOza?l-ef@CF`@s8nOiT0w z=KbftrSH2o?>`TcoA0~6o!ori^*nO(eb=!;>GhiLyXNoN9=S!etA%qWwbOiFxG(t@ z#P=gN-`l*G>V2j{=RZL0G~XkgOL;y<{I?X}6TXN1X83+`^S#+|a`U~}Z<3qu<^CJF z`CjgFa`V04e<3&D`&~wEK9{|a+-eqkAK-y5+Gin8@4x8$3HVIJ-zu&?j}XMXXO4F(-~saeuAk<3s?yIN z+CQ%6JulqkF~`9IiXVk|AKc87OI&Z^_)#xDPT_1td{c^V5Y_eOkY5aUs9v+Zczl|9 znD<{Z5A*(O=HYT0S7shwPj2Snf0CPdcni6i9}?tde%MQH=AVzq&HVEPxtZU-A~*Bf zH{@o%J(c!HGp{xwH}h&Uax*{oBsa%}3&_p1?AFekuD5o) z_(>)QtPn(MI@@SgDg zXx1lgo)(T5#}RW~=qTI|H_y9U|5dd|<}tVzdHTS^aC7~nP+Ya=PSkrHI&PvHf0`4e z_TPi}yB6uh=DJgv8-J2hjN^`Zy?8zHlu@2N$g>oAI-{LO;jhBYdY!RcHLRsmLdUhP z;`N;NP7(5(=d=A>Kh23zy|;<0^?e`te~o${fj7KQ!|&j>aa-=@Z{>8I+XRuvi@L`D z8^|AdQFriM6aJ)bPinJIeP`(Sw%9IH;X%0$q;9`O{NLc7E404^KVb5_sNq@o znNR6@{eRU@#EUUNuYo5QX`g}T=?|IsA9R8c@+9D~H#F>oH+dTQ8|wD_2p?d)srFUy zKf+_{HB8bEJLPcaZS7AY{=e{WzCL0W!n;1B>vdk%3H}7X9v=8mUuQ7;V-y~ltK%bx z{}(*mUw43B%5hFE)A_@4y+Pe(BK~rCa*Fm&x^PP1@qcLe6!HIrC$PQN!7Jh3c{+Xu z{Ja&q-o(|~P5+1B!S=czPDZ`Yn)oktg6omzQ@H0j4cNTSY0v8X$+=pNVug#~iL14* z%+kv}d1aQKw|~_2wn3u*2DOJT(+P~Ci#&s*?d3!UdE@;7`=x5rbiSICTW zKRgiA3Cw!^0Uqh2x2%~rRvVY=H0oxy*MH!NO*)SmxB1WOdQ-9;)D6>wb1gi$Q?KYB zu)dGM{o{1}A9XtCUE{5EAa)POvr^{|T&UyC_`DSE-=pJCL;kzrzBM{=6!~9;$JgsT z&%lqulOJkW3P1O6y57hF-Oq38#hig~-|scth4@+U@JAZP!PmfJuj}|T;k)3We`+`L zTiz;NZ*oJr7s8$Xx;?Xz=TGp&`8s|cd>uSguEC6l@8H2}b$nmM`(o&4+JAo!PyI@F z@DqrC5+1MAV8+#l#&@NA>ld(IqjkMzfBY>xdWp{eH1gjIkDso`X9tY)H{g*D+W!J~ z{;u;!*XjIokmo|UgYj&}&s4at5Ccaq=KKvF$k*^N@*FVnpKCDt#hEYadi~>dyqOQL zf%`(*bCLfc6W>mQ+3pE=Xp;8lQE!vgI=}DN+W!I{V7!<1+3-KYJzcdw4ljo%F%Hj% zzXSJ9(EVwyN4*CREYW#PzqNcxwboxt?-WpEGnBQxIS!<}rM$Lx0-;E7&(JdZ?s zet>%mb-d~4-g10$&wID)Kr?=Bg@=0RJk9iC&I;qXdi%~t{{8UOj~f0CZ~qGVd5Df5 zfjpz&iT8BiEr?$Nk6x(zEePKVk5_3h^)`@sS+(E$n)WV;zZf3GJU^>}eqg$Z-=^=I z^?eZ@lJh=wGvntgc%-xTQoWedX^pNogn1t6o$>G##<_`q6rMas_vdi*!)_B_puvpK z=5m}?>lLAP4uSiI>i)c-p>EI|c(kX^-v#;C8~>t_8)#mC`5x|(So%1(B#>q z`+pzeJG_eZdRK$lugAgzL7m6=!|>Subi5hQ{|{Z~0%!YJ{r@i|w=gcb6-IK)?Yr-L z=Qg?IP#7hS3bU_{5|c|Qw-M!58c8n6Z&EnYWePb$MlPK~jY~QeH6K3?En9M$@RE@0H|NjooV_UD}V8?$iIrkSnc823&z0L{rXF5JE`a6XWM}FW9;S*)2 z4_Q)$_`t?ZFLt~mKA#aj_A>{FQfk`D?W+uSJ!HLouTpE}y{#iGAm`1t*f-y=C+6+X#1Y(HVXgd{S|8@Oy9hwez2P%ko{y!`w|II}0C{-w5Z_=Y$Xb#s_R@+@A=aIo;*FP~$!u z_`K$X^L_b~&41(Lj?8uX6GeZx@PS=ikoARM8gM!Cu)iJ>J{{?QwR@faOv(AHJe2Ge z@bw%Ic5=G#aT!|3>u(W0^%bWN&H38I$t@{#Mt2Ui5bhAD03y75=91*~@%f zZKtI6TdmhRF6S$5F3EAi2NS2Si_Z^*kMHB-UM~D8;gh3|56SUu`a9=8gul0+ptp|; zPlkNl!2crQGpjg#@Q1$>KB)S@a{lLJ;nUjJVSjD&fY$3pXYeiQtu1`&80Qn{|1aQL$5s8YIHQ~b_bNb^oZs{Q(cSP~$mxUiA zd`#!*Vg6Wht?=nZP8iOe$Au5Q?t}xP-{@iIKf0p>Ulad>g%1rn{^0diCFHUhDa~hkDK&;WLUif*!soeDJN#C)nYw!Y89R|K)(6 z?erlINgwfXr)7tq)x16_d}y%)A-?@yz@^}@U;YyKtmypD6rc6~;QW(!`@G)ie@;Fu zeB#RvgnHdqgb%;r_#WbOkMOAkXlpC1Vy znsy-Ui^amn4|E1^6aBmY=;Kb_;lNA6KOubNNw@zWtn7dIrtrbL{D1Eyd8UO=54qqw{IiCY0&$fTodT|?N zKQlg!q$7Ml3mD@33E^YJU-q+2_x~r!0^!5!I)0k)7le+7|j z_&+9m`q5tb*ZYf)JNjPd6VBNKgby!t`cI0_dBP{x@d1N>xI6GsfDq(-Q~0dp59?n0 ztB*T%j`Ioo>p0=FYdU_E#{Hr2fioNr{GSp&yq3>5V~&S;{YUupElwEtY|qBAyl+JHjnMx#Px$b5&L_<4O5x)-I-kci?xVt! zot!?L%j-Vr{AYG^Jov+5;bT8_2HzK-bA_kTeD4bMTKC;W|3Bf=b>|=aM)fHlcVKnL zPZj;K!lw^&Jm}#E!e=(~b-CF8oIELfc1Op5EqrjX^Pk+r{f69V@-gA*8$Mvr!#9Kv z?&A!e^X;1aHsIfMJgoalPitMi;JDP3>?V9>&E>lTY^AvG$#pU z@W+yah0kv9lE2^CBo_!DxzzcD{eG|T;q9D0)D2d6PWn_FH7Gv23LpQe^9lO@yzrSN zPUuo4$@Rh~=JbyHPvKKf`-!=l_;2}7A9p-;I|=!FUHH(49N$R;d`tMi3C@2#|44o> zd~`zxzAgT1{7ds%$?-FV?=5`vM8|95bB6E%-Q%1u`rCw$EAIN9iu*K7W+{FBYC&;`Bk!4+tO5J09Y$RbO=egW9L# z8h20OLs1@dn()!o`Gh>;X5llxb_QP%pBIG>zuW0`yd~TH$HyJt)djDM{s`fTzOVag z;olKHAjcN$;m^Vck8(YPJmMWMXd_%P_X^0JRRx|!35_~bLfC#QYDU_aLhpZdPbuh+4XCxp)|cKV>_ORC@4L;Qmec$iJQce$wad!e`&_0|t9|Rrt(EFJ5@f#~q9E_fHE? zW}W{>CI2Y+UnG1$@7WzI{CC2KKj-wD_(!txTeU7fTA@pMiuU`vg%4ltguy;PA$(Ht zz(>UYo5E+5*N1sc2m0%r&(B4_$_mok&m0f)-9z|L#qlqT{?tIP`0X3Q7X5wbyjr#Q{#>Y z`u&8D9Pak{9{+Q4w(xWd$G4UKe<^%+XU8{kagx^qAKjY|3NNqZ<4&*W^tX%usPJLc zZ36xG1O0Wr?ni3eJA{uZP6+Grn(%=)-qsy4*vT3z`?!hp9Q@VZ!l$2f0mJ?}L-<4# z-`*DZs9&z8dA%Zh_$HrFkSDi_^n7@)o_7Fb$7Y73UslvyZo&DS(ITr|@UcrH2=Pw8!(?*==K9wZfuIA%TPC9*v zXF36Y!U@AVP6!|PgB#AR8uw4ahobmuozf>r8h5{d>p%$lJX`qakTVW;^-JOD zXB`iI>~-PeH#@E{G%2s)J-p9>pogJ=D~}6#-xq`r z=v)i?P4}^m{t}-$lRSJDmUEe;xRm@B@Vp>;4VlGq1d@ z%lSy@;cDSSH~D}eKAZvPZHr|3gPM@!c~SHubKEaAeVAmsja;7D$bOChUoLrPGIakc z;@}^rh<@l+|KDr(WOX<>GLe%)4y}VAkQ5EA9VU1q_@`s zpRYL({7h+3>-$gV6VAtDgpWP$c+k(a0l&c~^hfc3T=>9z2ZB5sZ7Tjha6GL0!NRBB z=X_q)d@m3_E&mhj_TIqf5a$#2?+WjB{)zf;4;G(Yg-@t{zJu`33!l2k$2~;>{q+I= zp%Z>W^#2q-t@9;}yX9s+?xg%oh-d1;#}0G;GP~qk!pB#4`NKZ?z3?gRI~|9~8t-xb zgE|MlD0%i4K5)77c~JNn!c%P&g|o?R!pDE*gkjuQgwKA!<=jzxa+~|O1GA2Yet=I2 zA34p({jTV*5T;Tvq>{D)3=J{OA5M}((3Sc05i6+U^3)Bi*C zzY;$9fCHfpx!jh{f95Ku4|cd?z`x^o@K+}YpSalZpywY8A5}lq%l_x&Y2m{fFq~hT zZRO*pt2kkZLk<-_y{_Y9lK*1i1D|%iT_gSv2p`maP>^TUt;PRrCk*G*p2CMWar{Z~ zKTY`fcCL7tQF62Jfs33z$p517GQA7_+I~Wa)j`Sh6875-0ujVjqLo- z!lz#K`F=%w-m#7IpMI+YVZQqdPh{X>ea{KF-Xl9)f^n9-Vi>vg^wHZg37i& z?!cFvKIro^!e`Y782t8i!Uv=H?TLVY)%gUw-DJDvlQoy$IGO)zso;n+qRpI$n|9_7pzz zg0IWb!apW_c731M6Pniv!Y8hG`oRBO;p5WhM?`;Dz}NC|_Y?mIgb$qMcv$ymgb%)> zSD$YN`fodZu=9a@_PT5aZu*@2WUt;1T?YNpq92X)Jm2WCpUF*Ko?z$q3r|1g_;)q0 z#lok4=5hx5)eA1q!oT&(GY8!GkKF9~4Ei|~{N3PF4Y$v*z88yrdK({ie~o*M@S)>; zzlZg@U--mXPJe>vpAkNFrH>orT&?JGR-f&ab1!g{bL^pBJ&cHcel)MkgpWMa%jaR? zi~iAzuU?YRyZSlW^|`9#pDX$~|Lo=SbaXGdx4im8(AP&~?3bvZ?UF6+PYEBH z*E_FMz)jBC==&dT5_|ET1A zO8C$fE+=VA_h&gac$S_wFx>3+S+~#6i_b@dkA9|C|D(dku6Dl~=5;!_$(cs`?v}tO z@?$TF{*uVg>@ehfk_bN%+~gco9JRLO{I2NFj`aDM=*K?c`q@hK>%Bi)5AQV`{?m&0 z!v1XupPlP`_7|TkgbzjXECM$@EQ~jeDE$k!ZiXB0huXx}Hyvp4Xk@3sGPpU(;(+Q8>q5ueF`H+uKW?S_kH>5m7%ZC>j~`(@R=v+1`r9J?L* zPp>_E0KB^{k)FpyKXaqghy8nn=w~AOMZ)Js`O#WT+{PV|e+yOj4+)=r&Gi%fd0Y7C zsP7A1w|dvGAcoT%W;DJ|KMh4A*Di|D@qdD^kC|z6^Zr54wIPqIvBr ze5+{Rods_59s9A58}gCMh0o}{d)I1`JR&@Km(MrY+bp=P%e=@w-}@owKXt3?EyM#m z2_HzE&z7>c4;h{@-@`;dU3R@4Dw<1#kKOHj))$|fg-@w&bFTK+i{K{bRHTRP_H{W2 zuW>#>&fUSy|4c;j`6=MWXCRvIO~OYb{NLg~Rdo4-AKr!yXnYn%{<969rH3;`KR@yh zzYsnW#l1_65Asj`pm$#Dl4u(~_z}m$_fzGC&ur-HMflPE84*79M(;YFE`0V-pO^nN zNp3MbV;%2b2LAjq@Rf$M`MlF`d&n~Q9~J0V@Oj-W zJzOn(;FrE5o|IkP0d96O6xqpZq96aY6Ye9Otb2g3`%2M19R{~~r5|@WgFT-pd~`YA zr{TOlUwkf!&bx)8AKTaIF@x^U^TKD};qwaeZ2e)$6V;cR;9WiM;(UT%JyY}pk-hyw z_*4{^E)hNx#X0$p__#|V{3zk$QNDjQxXCkdx9c<1rEeELtoxchrRP@zu71d1lZ69) z+_NJ;@M&=4KX{7kKj`fo;mHbaZ}&-`R|+4~`iAW0=imc;ALD40@7y8!fr9JbtL{ni z8o2p^B-+=dk2*dl@+Zd#KRfap*Mf&RN(J4IUm0%ml0O{MAD27G@tG*z-Vxm7SrqB# z1mP=1>-%Hj!_hiEE&l0+zF&f0{h#om5x1)lA8te<++E*4y8Z)wRrtUfjt4uOBRtv9 z^%>p=`676h-@X;x<~29c=gY!RjOLrpb^7rrZuum*$vJe8+e4856yamTE@wDTFA<+= zv`-%t{orvKFjx4{UT!C$pY7|y2R3y4EXi=6@QI4!>j{6`C!GK2vCjVl>E}I$XUM;! z=!focJ)9!?{e_RIp70etdyeqgHj{_kL>33&j!66maU)l4L3Xdkjo#= z@$JEl|GdcF<_TXvlK)EKCq{Apqrw+NesbNzo&P|zFNVR_T7DoYsy-Q(^q?+Iym7nh zGsLT(5k9+3?|PkKczAfJ&u@x8-LrRJ+z4*-y(IFV&x?Na3r@eQ>}RDBr(Zvs?{46x z&s2F|$kR_2{dAdD6x%QU2^B?+=>mm5zcM6|;&Gm4I=KN9N$>)0Y z^C@tf?|5WCSBZXPOP_DB=UWY5N{`{!KZt%d@AekX(={8?PZU3V5Zv@L_`6b66`PKaMeF!};ftd6Itkq5AE^3# z-=_HUM&Z+m<9A5b=L7xceO?2>ep=%5oa1+j&u-u*=b~ueohK!TK9`C1E`_KN0=p zCtUuD=pPdQ@ksvFIxgqn2b}+xM85^N$ulSN8zZ70)p@b4=6jj&p~>F;@?+ss|8sf5 zK7Cqva*^xjR*k#P5kBtRDDK)1-0Wdg_1mz2&lWzTxX7&{Nq#9jQNFg9%xa3Iqwrb8^uv?J5qXn()APk|5M9Y$GM+!`t&*1PuQpL6rZWH z91n3zP57ktMbPtcVca`i|G|DP5}usu@`U(vzTrzNM!(-L`r)gbPmq7H@TpJquKUWL zbbVeD>1}uLEdGaxesN^~7cPVTre)yw8;+ffZR~P>OLp~F;YryoC&>TuGWe_{{g3Ir zu+621-GnDK=O5PPpn&TfIYRUuaN9={k>8u>;u|Dm@*BZ!|0F&m*SegjxBK%S!^Lyy zkGCD=^BsQ4?O|N>YYU(GwaXdoWP8JxW}x2>UIsoYKI8v#K4fm)pYID#_wsSW`Sp!`@{pR2%&rIYW4j28*)=nSx zFB(BMf8*J_i@9y`PNaVpNis&UBJ8hYXi53 zkQW?e_|gLE_aj9=T<%@>bCyB>ZE%xwaWw89#Ajr0A9qXXb5{6(@&z9yN#6Sz=QBTA z_ZGOxKYN<%C-{L=49}2%TzpoF{L*iP4@Ca$t;hPfCr0t!F5um9Kj8b~gBt5Z(a%Qb z&QC=@@H1bR>onhK!!zW3TzrNid*0|cA9wN>&L_k(CBrlL>@E7~#jfXYe{{w&=r0oe z=sf3tz2y9n@UiHAVKKO^?@+YA-hI5wIdid(8|;5e%hNODe4prNpLhDdh~{|V$ya*q z?Oek%_*@5W@=QeQ`$XWs(B~EGWQpi!BKkZV!PYn3tJgl~fOq*v^ZlamnP|OkH9i?~ z-Y@>+E4%${pg4BL&-%E72ldW(3ve5EekA9J@Jpg`FB6|B#Y^FRlZU(X?@ETFzL(WM z=i|J>MN&upA0E`!foPjq>ve&g#^ksWRd zZt|=Y?Z=Oce)vLPuaIvZCp?*UJnWZC4bPDO=gYt!1GjOfqkX;J=N&&gI@b;mJ`(xo z^T5rnCVtc_&(#6n+2=c;`Q9!*>qma+RpDtgZs7~gfBHoChiV5;b_4IOV|1>aEW8@6 z@6Uu!NBn1nuN2Mey(js&1IP59Q~Mg8q35>fXP)zkhP?0pz)jBKXxu-Fe(bMqp9f1n z&x?LxWLI0C?DC9n?E7m)?XRZr$)ESy$(O*p`ibUsyYR_KZ?B5a)NgwC%Q^%SS@X?< z8~+8-I!?{Wra#K~U?&6T^xDZ8%i#abz&~2YY4Mp^$NB6by*(y;QgPr0TBG$&^>N3x zaQbjgrQjz2!YI!9r08ewbUo}R{wE0^)_LJpog_CJo?#Ea75_BS^Gc`rxYfwN?IwI~ z&t1~P zvBD>(qygdQ20oLn=Wzdijqt&H++eF3_crjZeMWkCMfBru`ncZ}|J6=+IcMIj%98AV zFX0mtPQSkBj}ksDzrCVwkE@PUkezi)t>{EH$# z^IPMCUCn&j*Y`fj`JCuiiuTJEXF8wZC>|aG&ywfNW#E?wKGA-8MEF>Q4^Rj)IVU%B zd%H_=ZY?~mJ08y2{e+M0?durq{1o96s?%U(7u@tS7R7HXobBU|{?_%ds^+^1xX~w(og5~ZoK<*s_=33YlV8zg1~>m=e3LIe{cBGg6sF!MW3X->%QJOF6WHiTQAA291!r- z_vuRFe;&BWKOEWr-J*}bcjZmdk4N@XJJ-h@Jjv}R#6<@SpZJo?`TzZx@PQ?+&tT8r z6h1!Ua)!8cT6i3Xta6@@J1xh2r1Z0g@PV)O+Vh74KJA|8&vHOt3HZjn`n=Zg4E^6G z`VsZh`Y1{A3b^TWAhPG&S0v|YPI-&;zpwE0k3Me5cMbvP9bBx~Nv6tPhf*-p|cyfNP9o_>zm@(hqi~os{-q!e^>0vg~!-&ym%ED&C6!oRNB0h7Xb zoiCxk^kBm?_#7?zDfQp(C7SudCvJ0nhPu!%z-`n@tKYCi+_rK>P**ju;+Cy@^NRParXo7`i&Qy z{$@XZlCy;mF7$Cj{Qo`U!+#R}JHJ}x$hCud?tV82l6-d&?dsi)jt;cnGXS9bNP2AbE5j# zg}_qRN+HEa6N?m zd#T||D?q=0U-YB1zQ4k{+$MZX`*CN{zp@NIt6b*t5C7NsTrYVx7CxlBIP@3I1-E(4 zi~QTyML(@NL+E?FPxJ#(o#$=eay}b2_q<&w$&w6T`lapBJAm37?vD{z0GLF+5`( zZxsL0Xumu!K9jmP-$T!C{q1Z%dx5XH{F=$c8SZ#|)=6^uGUzWd9QlWj>b2XegeU)S zeFi(cz003?DO$%@g`XJt^THL*e_&PDL*TQw@WE@Hf6#wjc%pbX)TzD&ZuT}9>F4*N zAFeu|P=9z<^y^3RZ2o^P&t#N09WH$IW4-e|MR=;ZR=7{ON%-_4U+`UBeaXLtPdwW@ z-?vVf{2L@g%4Y|)j=O-H{^Nf)>O|pVAMBOq8pAW}XQAk4)km?eXqNwu>un;^+fLv% zZt`0RF1tNY^a~?@b-wVi$lop$pTX^YzQO-268&TpSFClVk30M=S*FI_G~hMmm6HGC z;I{5dB0qeQ=qEnz^m)<$PWX`ios{6`SN^W%dyMNd$hooM8T!nLe&`05GsF-3FN3}T zZhgG*IeU5Fv#ZPbl*W1l+~zeE+2_FbT+WdPT+aQZpR(Z@at@1rde+xbUz?tsD?CyE zm9I{c+$DTW--m_;bbtOA_y-1V+hz+=+n+W#8J11 zek79rCE+VYe0I3T^!XF zw)1`5nTs9ysK(tL-1skv*Eex?&Ai3zB9Ou zI~(!;obUyaK7S&7CW-@}75@o+?{`u1Z2o=5)$dDxybs*Q9gp_W@xm8G=hTmu!RHRq z&x!Q*n()PuoTcl0+^HxYI0igR&M$%wBpW9~Z-kCPpT${5Z~p%$Za=GP9WMvBc}+xd z&LhHyBRvoN!1+w*y^;{`?IV2r-Om3M@&AnQ(Ulz!>v+EKfdThd;d_=Af}1=e(f(Tg zhmH?L{&^?i3nTq}7To4Ld9d@Zs%|zZ`hkegGs2fd>-g0(OwYu7=ei!ZkWswnM~3(R zR1Ig`(c^r;x=4-{KD3_8^IOUD1K~;Z{>zhr{<2>Ce+}I9GcS_A^ka>?Vy~V*EPVJ2 z&gV|8%TeIQ=fubkuNM7G+vzETbbl5IAD7=)Tl0FsaQ(jY$C{JQe{QrNKL~F6nGqk? z2LHYQxSeY=bw|Q`?zbEL(v0={1L8Bak<*9o^;-2O&VT%j-t+Wb!jp&HpTAvv4gxnl z3`F)eF8ZmboPK4k*Kb6>II4HO^{38fQDld^fZKcr|K#%QCw+cW^b^s3|DNc_Z})W! z=guzzeKhV8;ggYH%99A#yoREEeU$LCqxoJfe5Gi;W`s{f_=Yz)|H+8|fx<_leRMv! z=^@?P^~wM2{#+~i1(7{J9_aP`f#JN^=tk!=v9j|C=jrys2M%$&4RQXy!Uxs&b-&jA zEO67$Saj~(Vf3O|`s457GrPI-4{^nRgir76b{On=`=2@g`4Rtl!lxqqO5uwleLgCD zLA1W>-sF0iUa?mXn}OTBW+QzbDttQf=NAj#Dx!bD_%E#h{r-gLSBm7^WP!^wHtlvE z&X@NIpLy2tur414H#uh_K3@^OFpBr?F+Lgc{1M#FyD{y%K=MT3AMKY-Zgx4xRaeFf!Us2U34`CbL42yw{{5Hele>IfLf!8zw>bawapyB2{u>LQ`JUsO z3E#nR&c{FNg{|*MpG9z!e?g?rqlHgJb~s=B$3N)v3iA9y`0T4bZipWq5uf#=c@3}- zUHv@o^ao4Mt%N7n_<9As9U^>6|DH>Thc7fd7}HXp*MMi8Q-2em(J#0@!@9o$ZhBY} ztz+&smowSX=Nsm=2e{F%AKCw@!iS@My1@7>C13dUUeOOl~Lp zr016CN1}DVLiluaE-VtCiJ$iRhnGY@Ct9x^?vOmuemoL9YrRe}966Js>odeNmx#~Y zNdFHCpA*Ud_FrngyStph&uk8E>o_lx^YAXdW-=bt8!r|8V8s6+@gK>%K7$?32p|4G zA2;~1*TrWlT9+~bv&l2`n&(qn$#zGuWs zmW2=HU2koz%N%f%XDk}`i^4C7_R+1v7f12@%i=%1Pw%)Z{mS_-i1_a&d@QohlZBrc zt=G@MyLJ`TfoFwJZ|38M^J|TTPCtEDFa93zuH8oSI!yGF2lSp(M+;Bi;_DdJ_f+A- zihurp|Bv`5(SCnH^h1Af{(;Y`zjk>RM)Z4v+kA)B2gNtyb$?C^__uq{k*|u+C6Ryr zmFQ;=>>c+{!sCA*aGl@yxRd&aI)i`M65RAQ7_CcP^y33=S7E;&B|Q0Dubk%ykH3H8 zPT}c#&L{ZKhYSx!ztrb{L_dB}Fa102mEN9r{2O{^f5Vr0KK$Ae{Yb~xH^dtYiIcPF zKfeq%2g1ni&#>^3>pam9{dnhs+q@P>^SVp)$$h={@CV_8|JQ2|Pl(TO zR-oN4qXk1q7WKDtcwOQLhkZ+9b6uYOME{P5U7oWeKeIo0cU=zi zal^lZaE|CNiS&7=@b#m8{f77qyvxT8dfxE&@RzQZ{@BKF^f2>DpVvW>a|qnlaVT1s zFNl8lP?xhKW;X~=_3ve1Vcnm9iO=F_zijo0kDK1*3f@QbI|?7ry;3;$PY^zHrrW~} zlJg>PlXFp|&)*5(D)PfC|G~!{KD^hib}(ErEd8IhubbqCP zK0Mh~_~>a)|1HrU1a9j)75Ra2(NFK+YY(>t{!yR9E8=rW#3%Qd^q}wKohLni3cNdR zDE1ajV3!l8jNcLIsZ_{+Z5dRS0jJ&anVocd$hv$KVKw# zXm!^^ICp*^eClcEzrNP@4)LFk^!!@DBRNYmE@ylXdknbAIUf0eYeheKrRy`qKQ{@V z8S-@ray}q@;HxfwB02vie7fR%0{%R>jXN*W|JHwVd?NCDO>kS^^pnm%=;4dP$F6t& z=V{z4giouVD%jg1;S>74vd|~F*5AcH`kuoNft#Gu(K+>HqnB+g{c)N2Or7rQ75vrp z%b>qU^b<$9J`a}si@v5NJN?*YLbq)R@@HLm;D484GYs?k>^xl3R{Yvy- z@6y9R@_YA*e&{flC#=`A!e=LZUSU7JDL(TeJFor2(C*W!r&Y~hoU zKm4WfSz5vR{SomW)_HWK;=O?JOAsoPya6Zt)l$pUC(99Uj(=L&W-eZwD8%8&wS&9JHzKeCC_hjV9}e`d?Gi{bDeyx7-kGtFx*xXC#g+5gvtSEKd1Pxxr0 z&$ss%* zJ5+e`uip87R(uvjd?rOdGq0EauE6L0KHpWf?hhNDA?H(~AMZGy-9`UD;iCt*|9?@> zR$q`kM1JX5@GSlp7|yy(Db5Uar+b${|Cs306I`AUxBOT5%;$XHwIt{2FJ_Osh2e}l zdymrx`>6)}2-iRKDMjjy)3%_>mbojXy2_Sn(<}O-zfTY_ug~rVQ`aYFtY#EUvhjww7zq| zO`iiNI-d|9ep2|%POgWF3LRoGV6*Aet4~yeca^!-gPM% zo}r&PqEAop^}R^^Tf&E)a6WWRayDb`? zA?HU#pZ?zGdyC{eUij47uIIaiUl{P{e)E3eBd0t4(V~A2+}3w~bPjH@BwL<64QF11 z;vd%WG;rgyC_1NZ7QQgDpBIHsMt**~SDnvvB+n7T7e?pZcfd`a^sl~NA@04y@C)G05XB*fg4?*`4Q*)gInL;p>eq5>Ns=#%&)Lzuro?AZ zdEYxE^M6I3MEWegmOZb%3`d^XbzKkPyhuLMJofn9_QxcLA6{=9dHC`5Ba)+zY&1`3 z)sH>?@Z*lJAAe-?@LH+VO54>^v6j!ZinVs9P^+|Cje4iU|K;-ST)oriaA9x|H(R3#Tuj53++;~QfTK3%}TkP&o>Lrc3LatD*0jqy#L8+t&WKl^5tf|)6A6$ z%rdPvn=E9x*e;byg<`AKtY_)ARBh(##az8qsg*0JquFW{a>XLrZ1LZGv(Ad3ghv0E zs5H%2JFQl+j9_W4RVsF>&0M2hXg8~!O1)gJU|7{+|C9Mvo&Qwo)mo{TwmZ3cnnRD5})#pn*oI((JU_4Rl>7*K_$st6r!z(o!Qs+iAN|s5M*dYQ54Z zV)pHPsaVdluI*foUDK%N3Z)KalQCweg;FXltg4iru zS%Z12RchtSwL+;`E)~jbJaks9Vb!@bUud;aX*0u;%LU|Y6&p3YQIj<-Hwvvzn|mofX+UX{BCbuh#0Nc3MXAMyF6HWm$49pYJeSp^RSZ zX)~>s(t3?eQ_JCI>UA~%JF}C|uno3lsm@j@vMnoVJH=O2s_kOkrrE~7VBPs#t(Ns< zuF6U_3;57NCCzslxmLA}_e&c^d_b9PY4a~-JXvcNJIzwLh*5PqWri-Zik)Vy(9DqWFu=(MnidMn3vak{nh`Fg2TDY7n&LYg+q=3wgi3hoG(&ra?z*;YHlk5+SdmpbQK zyIDk|6^^GWM{2p%Y8T3tTFM#Q?9?h5o~gQrqVCSO-h$ zpC@Mu3*D^cOU*V%HgXkN1DtpV%WKpcc%4p#-O~SL6}xWXwDP&MlWSESmFRPSdbQ7U#t#S#$^OzB*4O^v|af%mH z&iF>D+Gg!=8dwr1C=o*`mv5GF`bFZ2Vy={-A+t5MQZP^~7daaVI;vQFr^Vofc7s!g^EQ`ZL#;vyKiOdb z3;=7Zq>VZ|ixVYnHaqoJr-2crt&Ar*I!mQSwbkMv%C%Bk1>!Km6V8iPxeAPZRIAN? z>#LL+xqJbg;C1S?4&i1kk5RBkv0p3@m%*u6$apes5u&omT5v!ag<4w7H5<7G;y3a| zEShMhScQ_Ah<|I%2ygG8y@#i#wil8<=MFX(&F@L1rDSN#@ouLP1|*i zd<$|y)<&aTs+pNoGi)fI%M-G8>X=(Ohg+zm={yVYj7196V&GIEEIp_VIDNDbO)BV$$a2w5)@0XEG?)~eWOyP0p7+2!RN zhkiaSaH1D7Jax{TT!ED)ylPEtH7cbGAusN=4~`vIbw@CF>*22 zu5ijA&bJEMnkz4kzc)B#WmIQ?7d2Dju=W?xZA88BY=wS8GJ^Eqo365|Md}=~a=qTFm8hWu$3v zQf7FQ1}>pk>XaI3qmi$b@j!)YvskL&85$)H-eQ%gJcY%}(=S@tNaoV>@U#du%FXHtI z#O8!Ej80gNO4)E`3r#}E{xNeXzunBWTWsk{op78eg9NO?`IT>AQ7ul^TCR~HEhkc` zT5ge-rA6B{9Jd_WX^RMh_?$zK!-}5${Od07jd4s zj9o??i%%w|ZPy6B?G&js2`f4cHd&gp)23KyvgG~qY!?X`3A1wTMxEoAO^25#RC2{M zZKSzsg*cO7yps_p5F3{A?K)3aIqeAkNi?g(mZ_!0Iox6+U&lRU?9Y0G={1NlNqMTQ zMjaE+HL7fBR?%`&PMRX=Wxup}43%V-T}mFC&#|QlkUPW(6ag?24sh~h4ygVoJJnW7 zfY4?|D`XpOd@JUNq2|~GBtm6O$WoyGCpot$5EL66qU_ERQ>s+R_c;+QZY^<`!!k3| zA*$_^a6`D{e5*=bfi$DZ`B1S)&f>EwZl#drHF0dMYD%n+zapRH2(1!ax2qLGK1{M! zEtlJ*cp0ZdrO_<#UwkT?iWI5cs^^>S8q=@iNn0EnZ36XD#(EZUnG8WJQDbDhI>{h< zCM71C#kR5JPk9cG%k%K;|3d$Mph6RcmmD_K!()S}m4HwCnX|p^Y2?N;J zRl*tkD^UW4tqy@PHi!M!(;AhFMuv~?RBN0I?P883ib#f7s?p$-;IO71M@g?p2uV7U zA+5RhO0G#JXRey+2Vo9HBDN=i0s;{YAxJ&L5Aht8pIS9nBu%Q32vABS#cnoIVqFru z5*x2X#VW&wYIqY)YszAT;dx^44vX4pHL-STNwqrXE@@rHlejEmgEj#dF$(&x5e$mQSP3H!6sqFhs9PMopSO9-B*%ymcsEJ5p}g{&-+f{-bX9f?y; znI$m_&VfUaK~Rq6W5W1=BYLH)q<-P z;^(TJ^tA$67?m+xUzOkqPsvte$25uR3mH)jQ6C{SSpx?N43}N}j}V+Al4;p=~i@fnC)q;GHP}*e0){D2}p5 zJKwRwV8)ZpDo0KQm)_yL%TY`!)rk#o_$gbu(jaWK>OwB#{Hzo!dCF(R1q53a%$0&6 zHOET3SfF5CMD5gkikTSzg%8p%N;*XnhI*w?#Dk{PS23qDZX`$jFJH>4VNj52*RZcD z_0V#K5-Ho7Ad(V8#gb3-+vLGohQG}DUN6J$_Ge6&iHU5W#kd%;G zBZo~fhuGX>l&oIcKW4Q;K#aqsoSBx1yYP{Am=&6YGr10DK8JFfnrZ)& zv5o(rc2T1WP$k%Gk%qQ8(kUopRz!;=a)ck0BJgmO81X+Ox>h{n7NbH+f;hE8hTXPt zJUA}4XCuw$3ORNuK`NDb6ip`3qPW$j;FF;t5(6UdHt8SsQqNV|m9&7(6U`Kxlt)SH zs5z3M_Ro`Gnpl$JElCGn13j{($Xm!cDInw0s@(rjFY13X#{pY4k4c1T^8>(rFnezP5mNnQQ-;97dQsh43=FgF0K8 zz^h)V6MyH+YycaR@H{JXWlCl8atat6FH}wnl+W;Lr4Erroj>5pIL)&5XPYpnUMw|= zm?%|%A~jdkMVQcTG`qeSH%1nkv7W^?*$v4VrcL5W+{h8sjlUW>Y9dx|K?LHA{&{vY zP6~;X0f^$b{h(}HWqr5=AdSS0fRZR?Xoz}kzQCdE+9(x6g?w9Q&IC3I^sWQ!_!${|58l#=4N zaU(6ed_eupj0l4a9gkkZK+OOs46+KP8VM|i5hpQ+Yf9axl;KUd=EHMP8o<&mwI`V? z02PRyxX5VQp<5}F==bZ^GO0qlO@W2wwmhnc)8mGR7#%yO*3h!EKI?Xp+HeP>ZLDAd#>aw&l;=9>`@5%+K}$0^22P@`jCrd&)lc?u)e;*+wBUGW|`qBFcO>> zTN&$V#X#1S0VyhSeMEr(FF@`>oXVAh)owf7z+_k-=R&ztF|R|0Lg9{Mv_UO_T-$Og zPMZquAA$R&B}SvN!o?e>CYQhj-_&V1_^V_Ob}gI7mNCE#4dL&qB-khw4U^|k+9Js# z87q?Z*0|Hc(R1ICAuWdqroaW36;r9AmeT^!F^L*1xR~rzi(Kv&3R&k0ceUI)QN?VR zxe#hpx$7Xgt2N8)lR|-os~|j4HOt~TLhyO4W+zYii&U40 zmTP2EBTmpZHjB?DChDJOfiX#UsZVq7+Ng5$(>uV3`Fw+nBTtM>iK$k~IJ3EK;?kCq z46cZb5o^N{aSxp1xiS?$BI{C>)S`b(%6Vy_Y_SOCU2duPR3Qv~?7gm;8;=4*# zxuINHY-YvO7*~Tk?g~8-7y(sPbFQ3VD9DPWEHj-KrCP?$Hgh2orkK@aNjQLVl+mc1 z5ccORWU$*~l%L^mi3qKrL&AZqIWDNlj5sZFIHwB5ACh|{BC+eAXO%l2>xw{6rXsGZ zxbUYQLRiJ7Va~)^1W6ftrCsO1rV3^^7I|(-Z9J-@E#l1*8=h(&kp>IiKPH=(Tf;W; zki2r4O)Zr^7Y-IXrYxZ062V68f09JJP@xpf?E#NiF}p^^1WC&Hin7%^IoHT4`=9LK z7^xO>FtbTmd15xsQj(TxiZIm%K?dG1s~ef+pjb7x1niF7UHfI;C|cK}#6u8k10lxH)uLphW1SyWx@ z^v0jqO$$Y%{&|)NM7j6o^y54tnBn@{`j@yb<>H__@pgkuCc_e_UUEF+&bgi8Ir1^W z9Q4X18je0kQJwIpQq73oC@<0wK#iF)WQn^Q4vQ9dK&_O!ryQXU2V8-hx{N35ZLU+= zWC2Lbe=YJSq~}H&bET|VDAZGYQ!C?1B0}O>3o#KpPdzG6_eGghg$fp0Ck7}3WyQDb zW;(mLT_z_apTb!a29aZ9Q!t{(h&wy({WEmi$x-34h6o6Vm})peny@%hO=T1-Q)z)KunE6G|3FJ@-jkrE6>qQL{LF98`W{P3vyUsIALxm>lD{BG{pX-h(Q=bcLBwIa(jG4 ziD;1uHTMXd!nDV557Iwo+Uig(#YxlENY={XO9Y5Vyocw$S}EtAezq_vr3TKv6}{N`pKp+evTC6WU~`3)2P#8*S0gBw1%%d zo0*awbG51~T~(b9C1aX5@P)S8R*dMMCq*54-EenY1=32v8m1OWH!0ediAzYZ>?*#P zF(%mt(La$F4wD)dby04bIlV}8T4|98-&!-9m5kV=jJ;dY12@ZwiPPr%Bi*3vW@kJd zc~)+xE}tQ7r9!O6d7rW%TxgQ(aGOk@jOm7YJpBVSAT%>>~GI#G0*{-C&STPdyKgpdFZ7?ZUt=vFy z=#q8hu|Y~vRR5lW~$hqHGa}{+2%HdvJJ-5r0hqEgmwVA8t;FSvFsA1 zMqXc_G=S;a*=FShDvKm_)jS54*`+3MUj zv*`(I`k!RGH``SrJ?mHF>WRFCx*vI3j$26Dp!2LW*V!4CKqT5S)nkB78d|B~*El?} zMH1x}5j=-C>2x9EM523$D=(9j)QG^Z$x+Q!3zsfM`U}`=P0DMTw|h)7#m`fGs8ATU zwpMBZwB_M$xml;b+WH(YwElTg<>RKFvR^kt@81F{$e!fOI?EEex=H~EyL*fyDYwsml^yXn}W zPZzIZ&68ZxV8kU7O6p29{ILQ0$0Xb%cejMFL*u5 zw1#DhDOnazLe!$At4y4V&n2#=n-NdK8>mKN0-*;ySI zSfZgkPwq+mrh)rzWLP{~m;y+h%R_qgnpNwwVs~5b6t2Poxq6HHsI0t<%}e_NS8^6# zQ0XJzAW0+4BtqtB;T_7L7z7@Wb~nbNy3c9PnM%6fASvx+oS(G( z&}B(X!vz9&fOOAtiIXF`#g@q>?d1|u!D5y-Vae&xtCGHQ%;yWdLqVsgmA~xFw$6Fm zHI0n@S+85C8fTH|m&yrtPfH?Q30M!#npZC@EX;~9=*r?!nd?15p`vxv(Dv5t{$M$q zmI=_(Rpr3y*HD$*sY0Iz={#NsheH9G9YE=r+8vE{oJuJkIm5>jhLp?P&vDLj4~%MQ zA}7RckhHSda0_g0mOLYlCu*h;N=(8n4419E*MV;#`X}(Da>kt@FU45JvR_(~a7#al zVu;!)MG~}m6;OJl3z7(kN71b3ht&cIm*nG;|cs|YwaHs z-@_Fi1?e(bwKd^%`(kK#qI0~^r0<37o18skJt;oYz{N`)cwEj|w)25x9 zOCnkw@HG8na?MZOm0nF|R4ft2lex4ghp=17cg?*uP(wb$-*V?lfrOGhPPK!bF*vC< zfeKME1vK(VY84c%`p4u%;&lZ!K8HLeYHuWwCLLnaRlvtci#gto|oS=ZPY$ zqQe^~g#8>{+y^q*Jbmj$f?Z-(d%dLpNurllg>n?v3%G?2-is-c)sYTTYUDzK-WM9J zGHl4A9=oo^Uty>8CsA19|8vR{8`JB;iOTkCWOx&fS*nPf01Z4VEk6X}By+sFX2nXh z#er&hW`-qTu#^Niq3QM|*&uJh!Sa>_$!-xpO^plJfKm0&lgcSMeZ4_Jht3mTk>k1@ z??pKSkxARBuM>u4joIeDpOOc`2seamGm0RVF3=3ornOjO@e9^r7ZN_ zFRgWGH!Nden{ZIFm02UANb2jncVjhG&P77G{wHb9AhTdg<2lJA%cRJ-9;+c!`Jg$J zDlJ`tS$VeQ(M%5`C$Xa%!BJ1GnL-2xM9fF09yKXmGU*?)!>t3QIwZx!h&5;(pyb>2 zyVinBS%M>n8%d%inGM?G_4t|#Wg%O!!0Cx1y4kMyL*q6)!R%h z$?nRmU_o%f1bM%R#Ecxqy7#C%QNJNx%{U#nkl>Y54m3F6SQw1*BpsW)e_&}HJ=}N} zy2JWcg(fb$=&h1O^1dGtvi1HFgwuyYFDBO5BJRan`k$m!Lh+W130^+QHE5-w{lbig z_DSrB`wptQ1SuKPqFdagbwY7|(wR;n-`+N0F>yk4EORBmy=GRwNr5^5!8=JZev-Nc z!V_j&9^53;r$$X@1{IsEG4u6KoqVQ^)3lSR+kV6bXP=UM&_qnJj~2BIizoBqnt~*V z9&Y+m?5z-cfr?m_#DhvQ^=jh4Qr78!8%G%W;5k~*PK#O*VF{h$lsxe6bQRH&mKFC^ zD0p%<+l5RHKWM?Um98!PqhZUc_*_JgJY=j2-Q@^OZyiymy&S{cJ0(JreO$B^$q5+g z9nCle%QOUIJiNL`e#Q&NRMQcdFt9?o28YQ8pii-tk#$(!Y7qt{CEf=hEu;KPV-1tz9WC`Md@zo(#Qr+5E6Y0jYMp2QI+g5Tok zG}lY5#+q@=8aniW7c$=$B-|ksK>5@$3iRpG&CLo~agmsYpqB%Z@<_&dQix_n*P@?Qdd_=v6!-)nDL@#GpJaswhJQTeskXrEooi@ntJ?AFx6JhBz;R_yI@Zo_TIP%`g-k~j*Bqc6q+paNGARBL%t zmEnk!tu4pSQ7&lupG1?q8ADKmFCZ_o;xs;h%QC9PM1*u&TYqkvVYg(7Ji$&Qq~bM7 z+U@9);^qM}pb^b(btrezn%F-k7ZbdSOVOPW)mlYqnk(}v2>+9{!Im(I7CBPZdQy6_ z*Q_ZvU`A9Xo3vUMX!9bvK~k0%t)aU9F?sP4eejA8FC>*&+=A7HU_c3sYppt0oU9Bl ziS$27oM&Ha!1+gCD*1}_Wzlegx!d(7DRUW_@izTW76_SXup=vCJvdxQkZ3R9I*^ct z4AS0Trq8vMu_^>vwx>;<&L(^_ z4Ff2L5*G14ML27|q(s>-EfEFb0B_oGZ_b^DorU&p1X(r?kMc`}*qkC|#)*X6v5eQM z!z8%8!i&N3W(xhG_(Sfj@>ElJZ#H90D!+E%P)%Y}5dx7B(mzS;Zx^p^UP)k)$l5bJ zSREftIAUEecre<8S$T4Ln>0QCWK7)4@OE;B4dJ)wWGr!o%n`<27fC&dE9p7mHR*t@ z5*#vXHFeTI(&HL7Z#5TQq@oterleS9*D~y0>mbP3+4Kt8GngGg41Egr7Bgl@Rf5o% z1KHwV-jmFD(z0nJtW%^ABzImmCMh=cQvH@<2su`IM--AGbgSdjDi+GGKHWERya|)CVTEmz;!4wVb z{nAn?!Fk|RDf_j^>+Cj&wv+DbZIna_=ZFM3&HA6TcU8ITA~(TU?TE47eL5;|2Q;5i z<}YE_tZM(0Y}ogK@iLS7N-4i>N7=w>fMU z8$cEg2rl*rs<4axF-hs!(;Pay6GU2$1Hylh%n_PWnre_++Buqe@4#9Tz4N^&7z`Kw zq{n0+RwB2ZXN})hG0*BUp#P7PfJ1@8HrXnbJ+dk^Z7!1lioHsuhmU?~tyFKv72A~S z99m{c2^oUIEshPhLf{9ltha5rG)j5zmTL{J9cYawz(A8Ns#!Ecb34V`gB(3s-%UXU zh!BVxK9Q=GSh%0YV!5cH-_tHins`EcwWVKL%!inpw_Hj4XpzLmc{7A=CGNUxG!j?( ze|Z71|4A;pIb7`mliC28AI&^`M+F+AC6+cM>l7wUt7SaNyO`{4D%>Q5_Prk5&M;fs z&K%z*+9J6R!yjeXE$@QUhhb+QTbLUzUcDmK=DiR)`0-R?FlHR7t2PaBO2ysEb|@ssXqmstvge4H1=a@>-dn zPCnXJs1jnOR1oOBpqgyuQu;zV88H}lQ`VVB)!vREN)Vh4^x>matj?}XEl|eH`gKbK zJ@4Y!&Y*Rg{vSeCk~>aQ3XR=s0p4k|m(eqx#L&6VpxuOmO`iOb$$MaPNT#=Qyy<}~<(4=dhHkbY}&D-$am0jM`dtqrIy_6Kv zIiffPt$t=^nblXr+c5>Y+Ob&PUZUBKFCQSqwhn9DHkT3XPhKC*`X&wg`i%xxpXAvz zh<9H@pdFt0guB$Dy{XC-2)2+REt({(WKN98ExhR(M&%uKe-d)&`1^G&5GEO)OMya?WyZK%YCeWY{h*V^Qy^a>vZuTEs8hC7Ex*09YbUIWCy#P3(V?WQZ$mT2gSi)D@AKm+kF4 zXX;#x+Fc`s4Qfmo8sdEAy$aro#IJK{OA!`9t;d`SAX|ZMJ3JIB>mQS|1mh-UzKSPL}}Ar2&StU2SYtL zE2`;!2}$0r!g<@NOvQ{MJLx9;xI7}47Y;aIr(YK?-Z3BsW5Y0vS*8MFeGBEomDj(d~D% zx6w&`ErP`VbM4Igl^J7l>X1jFeC{1dQSIKFq8U217I)^(tJy?VS@#Y!fMJ=`mZ*?) z2geB#VcWN&urqlZn2r*H(TrS>n>M5&2qWL^wn9^*!rxMgH(zNNv6w#%SN-$kD^;*j z-YO&lris$J7V|AS>#fYlT@qheLA*?i-~S}H&x8aV@O(`K_nu6UH=(dVu9)i#Y<&Y% zzO%j~hd1)MdnW7UGT5SNsyf_LP%kPIIM~f1fjl=68M-Bh;SWtpy}lDz{-;6{i;?AHA@H%jK*rCIf^f1dPvU@-K;*qfr1 z-nbm(d3Fcyh9jzdw*`@Y*7x!dr1NT6w?xgoA)z?8KbR{OEz5i9Z{fR(DBomw6UvwL zUQxxiMl3EuiAiZpChuV55L?@(B{<3QCS;`)VySyjcBkk?7C?1|z=Bho?^B@zjtgH? za{oLnUZcI6n3flFN@P|PeQD?50+(+H?7l6|8#lDH_CJYDTakp2i{?RVskeBVdM(v; zbWHaF_C=tS;rqA-XC3jG#PRlhgVumZRAOHWRwXf~>dl)~nbjQPwa*{E6-*N^RhlAGma(V!-^I4F$s<@LTS5>HqXelZ~r_wN-TWBN|A{G z3YTMF>&2I6(KCl1v^_(kLq?onF|f5gryQBo?aAZ_5$U|eW;xESCl~YJ?C2kpPEZn8 zGCn%gSp|w=^at3TByD9liMHK)aUYtYTefGJ1dQN>HeW7&=&0gaiCGe~(kMa6iPz~k zi2KK^l3Fl58lN$50t}KOYg(ZaZcW&6t3PW0x5gOvA z53van?EaSgjtGqQ+YF1xnAy{Obs_a;yXzwT*Hg5av;?(@7u$#JO{|Kv{` z33Fz?nZ3JLueG{21gLS7#WI-$kMTo@Izpra%;RH#?EMA zb9q#ZJd*@g?5%{LQ6@l#-&FjLH3YlL2PQ&5C{>JVBvA*z&Vq@2`w&gdKzHwnZ?jI+ zy~;o~gb#I+*C4K$lwOTP)PJa!9r0GoVS<(7$zBS2-E^&8uTynJM>E{VyJJ^lNDgstiv>!M?v>VzkRKD?UnM1UUDmRrZ>KyK0fUkkH&8X7@<7fKcE8eD( zL*$|W4ilfq@&UGUa$(XHG5#m}#@f738_L2}`~)5AsP?P$JzRLp=cP9Z0mNPGpmbs7Sq%N}dry183VVb0C4hRb@-tmWAgF4`NaPn_HyJCT z*iJ+8(4{8sXq}jfJUCes!y66QnevF91}gb3`PVKLx?|ke`!(#!*&zJO>WaRq0EMhG6PYS z?i=D_&ta)(;7w2q5;s_->>GmVJ1X;)lG%&Q$zwxc;l!?! zV6l_PJX5LxL|#V^RORFf0N%pf0kQ2oA~QbVhG@| zpT^Tl^h^+fb&Hk&D1@SSNKx4BFPt{6gfxB}S8AW5LuyA7N!*@9OcrN+ucUg$fq#-C zl%)4*m3vfNRT7aIj|sw|=Y^9hS%r*>#egxN=3XgPrc6llHH;SPz)i1687a|7;j*W) zdEf-QOipUrLJdSp0F!v&Z;=q3ws{~dU}voajVfTNo&~wuN1#RLI1+!LIBF<`2zBIN z+^6MO1bC4DiYqHt;YY_N!>eupH`JQpa|)jEJ$b%#a6W%pxhbQhE2~i$PI%uCM!*9F zYwS<>2`Pkc!0Px6XlG25LcOUjDs1#VX#KL>{Gh47Djy)^Q6Er@5yhU6g{R?ax{H}B z7?|(xM1d;=F-h?|A_w!359t5nksvebYb5u2_laVw8bB2ch zG?`(VcmeK}kV!6{%w4jCb~bJ>c4&qqJ_UGNFS0RC=e!ds& zcPdyI_ukW3G4QDgi2_06ouZ4lB$yfkI#P_v2{OM|n)X846|#_U2+byh8SA1Dz*K2S zs!NmQaCv!%4x*OHB({XPAyzKtM)en~3mdVzW3}+*ENa*8ivXlKO{X05t|4>(? z)v<2tKf$sQH<$-~0O+faV9~N@rKDEQwIi2t24~X{-~n1r@8Ro$or~q6R0F) z3he3#(O$l~X^GCe%b|BFUjupCg2k zx};R~aaV#`h0TS}jg*3XxA29(js%cmh_-eNCPu+_uO!a;by7feb_q1G9XyR5KqC92 zdO&q@jVoDj07DO_O~)8c{g5~pFq@!bd=*?|El)>|M``#3+RfLeGl=w$Indgr^)M-? zRg!4#J&{d2{kWz3EZ;=Sa$OMHx7M>^W5r9e^J ztk$pN4e3+~7?9}D+P+Rvmp-BmPt}v{N7l#%ptx7fbcUs`GaxK!}DfZ3ndNtfY@VI&q1tajU z*vA~;dlHBha}D?ME!-9YGu=d?ET}>re#E_}`dBo^jrS8t2aP9qwMQhrL|**`Jj$Nr zUa5J6@k4#9G$WQ_8)+@taG?@YdAxOdz0png$~FYPrgj5cE3GZI$Fh<#&?4eumYLN@GsnV8BRhgk2xD3(Sf|sbvj`Nl>z=wmgjiHoF2=^fVUF=x%4b`fcL`(e53PO& zwG+Rj_F3;o9|V*QscMT!7kbbboH7axFH1E5n;Cn@sH1=9UI{g0aXM(Ia}xp;O~k>zVqh@%tV3#a zOyj-MjCsqO0b3)QnCxX(GjziGyBKmAfb&zn8QC5k+C+*5G-DLyKY2ziD6yd%8#iY9$nlwR1K*nlaBrS3cYx00T$5-KKW%iI$G+3B2=HE5plZ&IN+Tph9 zQf1%$o$7g`i$N2O(FxbJJMaMMm(tAKz$mvBkH`145(v6T7Rr`+yMjZbIY0OyitL0l z8zz6@y{BcZ9OJi;g=)ciSNDPljMYFA<=t~fsqkKj4`w#=$tYIXFz-kx%a~YGfH~2Y z0c5-wZU9dgS%wdVi!86kG2fq$7g!Lz^bxQCNiFxDI3ys`$`p0&XzU+mhR4t3jZADc zK@$VMjNh3^!W!%axwA^$8}gbOFcJjN+TkzYS;sv^f8_SibG=^E^vVQzXfzRc7 zzQ((SV2NAae>G8tENF&$K*r7+s@%;~IeQdEADcca0$2nsFoHzugLE=6yHxU&6+Cc4 z;MH)ec2cW>p!l?bX!Mik(em<`V85oBAuxTH{+R+Nu|!iNZykKA<_6<`58o&k!k=s+~pHrg1#2FFn4mtMv~ z%nb&mVfzUV+PM=s?J}sSFlvU3A|KU~SROi+qm`z7B=$t(U8w*$8 zE492aH|g^;v%|u!FE~WbqT`51-Ggw6E}S>x%1*ZN+M(OZBIn3~UVb-9R#RtaIbEH# z@ZJ-TLoftr*-{DO4GtwfVHHpz@CDGTel`mEUTOZsGl|0f1W2$?SUTV<={*N$qbE3% z*2HH^m2)r_H*Hn9lzB)E6M*B4Z1yIOboHSVDU2x6bp#4SVNQCmpJ8y*SR@F7j@&N{ zkEaFg;61&{kYZzUaAHOi)P7~F!h_VlChd%OYl1%Gh`@{i7DFT%N*kkojxBK(S7`S5 zoXqJFHRlkiK&c*DOt2>CuLkP4@fKGr`ghe|0PX1fVoql%jNoKIsI&YjN&xdNft_+LZ@-;|7BFBxa zIZImgYW#zTF(*w{RCr`%U?&v%At{gGv(0E?1V<^wh7vmQbu1iEDqcz1A?-4Z!FOYq zJt`jI-aNbJfU&aqOa8~4K+3>`C~U&=!ivhcVRWmc&b9runVd<(`g1@)RG*(AR*foB zdw+wbHC^(;_;juK4D&v)qiSeMLVaCd_p5No@*wBq!w7J4Ye0DVGAS8+)}1pAdU~93 zrJcf6`gkGg>jfLsb@aA6Pkm&5Io#oz4)C<>s=PYeuvux+2o6I-oa2M=k~}$9wvBf0 z37GjYY89!IY17;*Mga>!;P&%W5N}O^Lnz-XpVme-$yd4}Ax4D^l@aFb+6t9+DhKCW zw|oVVzBWA1#RrIxpgrQ?o%=_SSKb5Ml%9Lftorr`BnmCm&zQ;7Zbvfswz7lxt-o{B z_E3$dLCiR$2#yUBP=M%PnpPxUfX9h3r0{#s2qBM&{bW-2Y3(ivK^v!66?LbP)0CaH z;vM7)p&@2~Xdn)Rf%+T$uGTS?{+&k-ag53pO1BKyfOv+0Q-&3hx}j23W4~3dVOM}b z@6!sbDo)$X^w37S*Uc-OJ(RfietoFoG->-@X-6r|A|Dhi9yJ@RwZ~3IG;5r{GLnWo zo>moJaEYkI6Lth(9JL({TNSZbuz!)fMi%xPYYJQieL>_s6Vh4a3&Pl0C>CGrEvM`N75K0 zT?_2!0hDWlf|+1-B6aWvUR-rmC)S82vIhN9;xz!HsH~Wxw{fE>n5-U9!^$Fc-Z$iQ zGOCtzOXQ0Nz~6E=H!u(pW_@H&6OPA~7^&v)G!S3Rk-5?u0PFS#!RCYl{hXkX_ZW_wvUUfV$K zKf*?n?V{TF+iPZOJ_~PRxB)3Tq9!A97bVvBf!ngs&Z{2@C7`9N~Q&Y zm>25$)5)Pxo|jUcQLAaU?3aU^Xe1hwzz3m7L+s* z#RV}hg8Q}wsD9wdl9X&bs?0Bv%-EmYW<=GN4zV!AR~xI3 zp%6tYr76i%4$%J^7z}Aw?{~6|?Tw%vai}A>&{ogkR}kfC-pHM3a~9 z@5E}Sm@103&^dz6E(J3Thr)+b6ns@d&3|*R^k2c6)~u(LjhI#PCW18LU4uXb3Utpp z4_$!zg6Q!T97#k>2B7$z^mvHSoNGyE$l2wOr^fQUfyIbIonm@t>bT*m(B<5iSfsFp zzka9ifxm5vnP{26j{3xSLA7PCp=ql`(N*h8mZeS!!V6pG#!T21!j_3z3n?6oL&{Of8{=FbrYSIUqE8d_32T%P7AxeaT1%aQ)&T`u2oM!kWciHo z;7oj4t{N6-!^pq&9(Zr)L)8Z9KVy1 zDHAIg(n&R5BPWbh{~lgtl(35A0G!<5YOwEWA@q?CBak5!3z>>BxTy1NK~-k4#rMJC z+0?{m+?UZ(oLJfjnARnJGd~n+OpVaP{VKfPq;hV5fJm`huc;%I=;v%qmV77RY93v` z(`sfo7iDo+q?QL3_)v7c)C89k(HJ8~b){H^1d@e2a$d+Zp@CCXWCv+Tipn(Gbj@Sb z=VEgSi@+)pEW-*FmdK(_q<%cB^O@ane<$N1V`K8PPeX;8K#0!shXdlPyP1fD);2wKE0EF6f9vUu83jpY~XveH-IU!F6 z58es@vbs411QM~g70;c}Y2g0y_?rPZ5W@ToW&c=xf=4cb?xP7a;R6`5%xkYOQ&%nL_3$0vr_J%) zu{&?-H?_{m^<0H0KFl4Xfc1en0%Px$PxA(JjK{8pmKYwjKr%Cz*r6Cr<*@daAFRgT zX50NJe26Pg*C{ceLl+y(`d$fy&RQ;7cfq}8H5aoKy8w-E@TgBFmFNU*k|V##hMaz$@lGZ3mA0oW))02DXwn7Q#)n3Nzp z(Nb`?wsQ&aw4j|08k1niw+&WXnoba`OSDQcPGchY5x~t77!akcM1t$gq5C^knOIOA zIynoxTZT2?m-9tRin-L$Ai}RY+xAp@0e##_D%T&H69F7U4CDliicU3nb@eEA7NOpy z>;-lfvX{QbQp;pzZUP(%nyH0KO{s`U~LRI!4Xs)r zUuWDf;plzQqfO<&YfGj+nx;Ho>^?06AEFE0hJ8}+2nTg1GU0Yy5^4yE2)wL5I`&no zT>{fii5f=P!SWa_#=bh5G!m%KQ1j!SkM+z08_C4aST?I0l<{j#g%k7$7BiI$Rsgh*wva3;=8!)EMUuZXK4OAGle{Hi z)U^qFRx4*GBvWg~8Q26ZhbO5{ zZAicqcdT0f0fj@g!b+B-bNPpKUQmjpD?BqhSI!e`acveBMFWy~AlXjFO7Itb$7*Gp ziIbsu@zRK#OHM$rDkvL3GBi3vD3aL)W}JP;iYt_*>4%#&g4vWro#y_`T?sOmDSRn- zn%*qSG_Q!(m`g&X`{FS zf4PXiQ=0LrLR8ojnj&w}#}TRPnlICd05laPu5iR(r5KgPS$Rw6w1mOcAX&Uf9hf?+ zo2sLf(gBf`L#so*Uk;UETODx-SHL@IaSJL875uwaoSH=#L_(0OZE=}!e9saGDBOST zv?(TNk;K2`X-0M@xUxJu7-K{ecd};5P!bW=Nx>ph?1#5vu zkuQ51loi@g_1b(bU;Z7LsBiLRC-8< z$l58AlI4a+{ZYw`MXjezW zNB9fTz67U8pBVLv7!%4@F5|-&ond!vSDCpL>cRwX1on-4S9~)o7{`B~HnE@j?ZswT zYUkh|LN->B1o;9Ani#NLv?$+JwVr15q#1}dfNvqX5Fp6Xz3^J`$GUwcO*NyQSg+#? zMF^O7%8<&@n$RoB0wQ_br){{V9!|biGYio8l365;VbOFYxr}td!MVCKC^yKVTFpuQ z+{Tz5$(DUv)m)1RHo1lq? z&nFWI2(EFgtgvEDSWMQ{y{AXPv04E>B5K+wHiVy}5$hec;4^tPKxeFY&UU2c)5ApG zQJ$!C6_EzY7qaCurJ!PFeNWD{WI)^7NAk-hls^21hg@*`WFGEux&V zBv6qx3t5!MI+q}MS~+`vr&wtp)uXPvR$RbC)Q*tS#o+}LCHp%-^Jr;SCfd!6-nUR26zbC`B-)n&x-_Nl6aim=u2Aj2%})@s_2w_uo)((re*Z9;|myJ z4eRky5n2K!`GdZ7@5w#{IqL$#aBzyU5#qnXtG*MB(x0-VCDnVSD${4FuU0mb3Zl698gV5Eq935)SL%HC@<(<633wjTPaC|JhmC3p z#+dhk+68(sQcA(GZaoU)EE+?kvX7C(+&oaNpoF}O|7keg`|*abKuiQ3d!4a?x>#7( zYZ14ONjB7=o32TYCaU5jiQhJea*|Cz*2TSW>HD@41;Sz!qCWQ zyDu0(XZMMsgw6h!t+G^1ZWbub7>cM5s;Mxj_H>#~M|~I|RF9l)*AX=lEgj^gR+jTf z0hScB*?Dx>e~w5poW8@Q0Vtw;kIv6gOX-`=_k+!Vy|n7qxSgYs?lQh-0}2hl5MYDj z9@`sg^c(J&a}q_>ti+$j_f(H%T|h8Nr0(gIJvB3<9q6S68Nz`XXU(g&1P_S|{F!P& zqfRL^yl3prF?h|1qCl*UXUm-sNuKS%$?6ouGg?VA3bz5|$DH90O;0}8Zgp~{Us58e z-y9xG-6Cqw7AN%5jA$Y*8lb^mq{@wMCffTDJm#RIdCItNz{TGl@0J0EqNppvw#t~Q zl!w?0qSBSP9U6Ml$~gwcjQS?^*&#TVj8u7k#E-1LK}@R%c&i#jAmZ zctxUie31yRWG3(#^*VK=24Pm^W}VhX@W+`(+QI-h{=jHIgJuSaI2N7S%H}0u_07ewfA1|_v zO@So*%Ba>eB7uqzN)DEXP_B1M5u|jhvLa1K`f5GY2q9V+yW6_lgK`Kzn~C` zNehT_5aDdUvq(kAt=MO{a^ySbw1@*G@>%8?P}$4!bTs|=a@~m7qcq>ue-yuTD1}F`@jfN;RMY`1Tn^ zuuc&*(ZHFYywpOsg!<#C==4N}LL20Q?CY-^O@U%(<|2kFtq43`-?%4MD5)0k_9tRq zoQ0v(&7#=$BZ@MtW@3ysT~=K6Pnj@5*{lD-qAYLQSyYG#50G}4S(&}YNVp{Vg; z!Y+M>cxCdCbc=kmb33$e&Kgl$G=!0E-AOEnH>P&OpkNL47>Gf9W3pLZJ0%K`=EtA} zm<2?EuGNOn$Re}-NWirpj@Sn*Vw5ytAa4yP(@tPHsnKb$;x?MJ+{3@b&w z0bv?gsX>?GYLfse$QBV<5We^H)>~uPmB#W|JZdqPJ{HQygvP6?^4Q4OS3%J^Yo^H^ zJ)82wW|#kr^iw}8B@s5`4cSf!aZTlv9;i=3kXZ6YA)&sH)ZiTOweQn*QX~?JoRN(Y ze+=;p5H>q2FM>>ImtZmOsmewHcqv5M+_xZ{G)k|ki07qSBlUI*dQP1&y&!L=eWwjVTJUnpmQkSSlu@h4J@MmBlg< z2bt7~kb4+s#z;16X?e~0?c81fBHIsI6t(0OZDg@l++Yy2L-)`uH{PvZ0~KigcH1mn z2J!HJ8JDR~2n=AzmgE>f?(dYLnp#CwYdV%X(|WK&g#Z&-h>t3pj?5Lo9;I6SFjhR*b4MUHxIp4T zVX6k$Qy_<$H0k6&QO;CA_n!8=`V0PsnS2g!NnNQ24*aZ}+hX&$2f zI-XhTN`~OS(<2zSKMb#^jyv`@ogV?nATXef#i~ZNMV3l3rwpZ~>H?3D1K@wyRT~U!0*f?UiLF?E5EGp5K)ebg!&t%sKM44+u58l1H|cp$=&W4Y zuKZI@UVl!N<-gFerh2(=2xq0ZZ4933;^}%KIxrvO7&Fqrt(_FL<}q?Q()F@*rsl}C z$|9Kr$3h(G95nlc1#bM#c-Tf&`o93d@jKH1>KtIy6&#HZwO7!q0IsJ*;yEx_p7?QO2l2nD>-?a+vQuiHZ=~* zl12VE+%^ZVuWiQ5sF9=}Etk|@6(?Iyt|k2rXb5Itod^VJw5pVGzb6ua;Rke~2^zY= ze#q1)b~Qjw8z~JOJ2@HA7-~j9GkZiZ%rw&y(uPjw8?xZhw3gXvhhYWW`7Nr>x{{BubL>>>1Te(W# zr83kcR-bs4TvLhP(l^J7XD&AwfbGmm(2K`mBEtY<=pS$_8TWm34xI^Q}j9h0|f5)%-zwCG2CRz1w$eE&gv*C(AkR!(l%6X|V_CtC)0( zg&TN3Qm9~u=jP}!eM3cOtKTafREr-G>Fc8Ea0cF6%~sw`j?<=hSQo0yd!?c!o)4i!(n!I5sw66fcLV#1aSC$;WSt40%bD5KsOD)huT_!kPKF*kxdheA=)TZI^ z(`XE_cAqIv(mc~?zy!g=JWfiwSK@bZ%HlvP9WT`hU#K2hnHp3LYeE&Qyy6hsn08o< zAk(4dTLdzsiOAG(_PC~+vce!f)B}Ed83&DNUR*5zoP{YdS`QoR8vkb%du#V;Whwx| zza$o@VF6Dv9|9t3$D$Rg@jeVm_ew0Qex9J47YQih=1m!=AxOoH+~$H3Nr=ezN;0P+ zgTT_fwTAX8Iw&%Vb@W$BStUTv8o!hM7HUtRsxHxzf!Zo+0?Ag z!7+vwi9{ZWk7DJ~W-696TO$M8>M8;W4Vl+A58rYGDR#Io#rI6ZC?aHtWG3z3-${wH zb=j#RS?p4gi?Sm&eYR8l=SZ+(;dnzFd?dLnM*tvBA3ieBSpA2Tl!_FI^q#4lDu=0f z=zNyh@}vM3ms&@PoTAwy^EYDlJ}r!=dAD}E=ImAu6mHRzc{BXxj}2I3VeUQA1yE?~03olox17W;{wJCyQntZp4TT;RfJ9_V z*Y410Lm3R)g7SS+uSO;iTeAQ&-mQbuDUk#{9&>YTG`bUxS!yZol_bqu72VHeW!WO0A~c7bc=k#jp%E4S~}8a+>?huc=q*P zR2qiXV8zg9_!ePMKT^aNm)VL5xStM!s%&s1n}r^hlSzaTS4V?~wF$?`a4-W4Sn` z@`oOsdZlKBcx4*^x%nZTl)AcAqlJ|JY`u1j!4D7vrpM4FHgM20QcUIXb^Ok@GQJFb zu2WxaMtb*ivD0j&XhyFAHkw>bE=R-(a!JJMKnkM{S;>831wTETM59i7JMP)kNF*1XS~?zdsMvBmjI!*-!m8_vK)m_@baOS- z4qeks5k(O&pUWm2oAOsEX~f}*)Na@>0c6#7dOr0*61@y;Gqkng!T1wMYq_Eox&%-c z*HXA=&?FkwXWzX8c%nkQS$iTLj zeeEUCgzd>WHy{n&$_z>`*CVul@0E%RKTrM1a3XTi2aO}cfq9{-thiCVQA+T8rCimS zDP=5Vc3mR?mva`H73ZzVPZBx5&%F|JpT=Olg5%GLFOA{=YIKBhfH$xWqFxh2I-DEb zCYXbH9WT!^gv68ae=(FDFCmU2RL0ZlEjB}d_DS3)bqN9-o=9%|0MgHa9bOYwVPwH% zDz=L_gHETCOj*SlfJ%XiIgBL-mZg7xC)VBR$Ocr@jt;A)<-ioaZRdbAg||6A6)X%;rK!YKisn8@FWsBO#(TWI1#( z`N}{6fo@@&n1x3fmh_kfp8;ha!xqv#yWGN^v&|HxaW@g%T0(JK$!begI}H*bPpTa8 zHen7dhW7l-qFFnD)`2pH86uY+Px^R6gf?WNr>lwWQFoa*fGR(eONHCLHWeQ5`-b2z zK}uHWcp~I2(!w9$F=xJDY%u$mOvK)EuN0#jEs(ng0(2olTODV+p+eXcsN>B&9rpzH zSp^=Rm{9PjFt}<%F0fqCjGQ4_BdqT|0W2;Hgk4^I*T^Q#x8y3cwGfOjoIJX!$ojL= zNCayyQqEK0NaL6~0T=_~@5$kdPcZgJlVArvakv>Qr@s;#$}`%M^D`NZ0nX*`gLw+& zHXHCK47$0oL)=SIxH2RHmz4I)U|~Dtz2oXcW-}TxyKqr9kCPKotQT*MAetRpPI=)7 z#@G-dIo23VI*JK3t}v|=SqydC)m6mcDB^|uRGdr(LZk(&a)=qy(+8}Z?zql@!t+to z)Z;R`l@Ro!8QZKHNU^^rP&@cW>2<%SkdRq~jgdfx&TQ;G)m9bj^q9CUh?Q4-@d-y83IPaBe zRB4i4=@F$KULe$TZp~VIO@9QKgjt&4px5b`S!>a=cqI9%x{?-D4<|Le(0kruu=^^=twhWlGQy`}hQW zeNv`g9MFQL#{mS6yH8!>H$dsRjv3`X?fZ^j;WIMQzBvz2qE-aV7zsoe^^tri;CVc4 zz9Dul7}!)hAWkDCb`v4-jIg)I1UKJn+_Q=63KdZj2i1TiWw(dH*Rx6{cf1Ak;;aL&@GcMY3^(m z7m!vH2=?-+28_7@slhP$Ub2)O-S(^2UK+O0;)fy6folUMD^5t-$-lK|M3~U%sh81~*j~`{iO*DHk zlYOik6sjkbPb^a8pHZOHVH*`5?4>U*_36W~Ruq#M540V2Ie??r4}jCZj^2bH?pL9w zVK@*DbR%g6B(hn(B*>n>3*hy7%tIL8(^MuqzbTN7emBCU5t7scvA!W{%5S0rm+if$ zo|qKOrX4iF+h;eK#h2A9?rE8?n2A)*<4VYcLZBFv>XnTU0cOGC>rg~Ynxe9U*H%}q zDUmrVS;eh`3Rq4K{b&k3>9CHbcUCpPd!N>SDLt#oN#4<991N@`9wr1KNxXz zpEkA&Z)~p!dJvycZ(cK{7ny|#G%M=aPA z_ntyLZQUn@g3w?nY4sSOYVM4HA~Q&FIm7Rj#vKTE)Md@xz)UBY(1j&IU z`gZTIbQbw1NW~lY%LAg1fCx-{G z&SJBe46wagjlESwi-n%L(R>5i5(6;emr-JQVU1ZsZu2!;^pD{w5?APiukg+gEPcBP zS@&s`Li*5LCF46`0Vh`9O$IT>G_sfS^BLZCuVi5G(;zLy2ktgz*C_12*5Y-H2 zUjDsOU4h{d_b^YCVe$$%#xn2l1lXZzjf}Bcr0$h)yZy(&rXK*wgffO2F_zRlY#}C0 z5EM|y_mr-x6{yc#5PZG_m3l<5Hp!b9P5@MF8?n#PTt1w3T=Yj3d95?V6$spOXeGwL zG+ANn72HECluwoykMNKS8l&3$7u*GcjBN%WyT20(W>hTsSV-~4ntK~#^&Fq~G~-De zzR|r>A)edkndQhCM;{Y?v6I3b6#q=Of;#53UiL4vnV(f|(WoMys3Zw48qCS4R)ivA zbr47-vaBc70mS-aIq+JtJH%pe6RFq`2DtP4v?3gN$^w`B;B{dhkAfG%;d;B=3&Kr? z-g_moR#&ru83=2zSWROMJgx!z#1Z2*pH}JRH{sy+O7vc8#BACD6+l2CkjZ3va7LQ9;PD+QRLz(mE+~nF0V7LO48(k zv2HyIFCq}d9*nqC*(8T+WoTYG>C1VHQt>(uU8teZsy~?$>{EOzyQpd5fFqoW&hKb4 zW$Asl)(Vp_VL`^%6lB?g(7H}d2vgZ>TazM9^&D5yZ4`nWPq-jvDdaO;1j>-LYqWGF zi^e=ltd=_*x(Tf?cZV%7H405q6V##*v}oKR0#U{IifjIfK6k83?U%qYx;!a zFn|!Z+IK`S9F4>!IOFsT<3QnwOFRy6lI&m)IT8qSGIkk4otLK}3OTzxrz5>|;34wq zXVSG%X1Xd@w@n&{r)6SDAk;>pKxR63pn%sS($}1a0vxN)zE_}_K_+@+IKvBh2&4%E zoJ~qs<&36{-!;@wvGx&03Hj8!QI%2utq^5^&}4Rp8;>lg!l)R}4XvV6uo4h3woKuk zpL%H;jEOR}(Knb|9UqT#s z9!>Aum*voVrIkvGmg6P;I3lCcDa^D7om$BVU;vil>xc=mPEJj!hrbSCr;FS3o4KF1 z>{nzTKpTzP0nzr1rDSUHM!jfJmZwjTy|6@66?t)5Yvz#x)dKT6K^n+};8uHd<+E(& zfyp*h*HvAarI`p0k!ZgH|G^<>yijj}6#ei6g3_u5!7fZfP2IeR<{Mc`&)iER3MgV+ zdk6<*)_u1u4?Ll5P-8*39qEd3^N2j8hfJ;)Kd;`1V8RefY4enDe3lazKYxI+H;fQi zIW-rPJ7>kcCnrvGLZ}lO9mMNIJ#41cYZ@84$hJ+S8!fXp}aIYjBv8I@yCsX^j1s{cmUOL2- zo^VRDaz?pxuVk`gvPD-_IHVuIm#)Vufpu0Tnvi>BPT|v?Qf+32DD$iq5W^n`il}RN z6{50d$KCeq z3C$#vFK4~Ym~Th3Uam#@E>K;cnjeTM(rhRybPnnLDq7~rRNewajg#!44pn8)5f35& zib|@%<-^ea5n-xkY&K=H32SrE-3uSQ=TM+A%V|q5W0g)p%9>@D!Hd3z`q_eQ2p_&m zGhyiUK5clEZBY4OZeUV9XeH(57Ka@P0#FZk_o(eX7>cN7`{E7W)Y!tB2&x^e3AqXU z0JrZwi3U#O&QC2+0+wn+{#kDqrQr%YFN^(}F;{SRPyn%)Pq~>H27a^Rx6|WFX-iDV zx{~ib)lq^g0Iq^>CQd%sOfgIX(l^5s?pjJ5*}=ad9$=+KbpJPBOJqxBnwMNNg!HD? z8&T6+t(n|1hpf2;ykI-pI#we|y@71;c69>tR4Gqbs+0B|TN1bVb zqPV|PrZ<`g1Bv5P)!CF74J81KK=I?LS<{g|3oz~~O~sbYZ*FsqiL&UrEs zvFF~C4+0BdmH80XYKGX_%@9r%K$I1T%Gww^Ta<%(D7Iokt+c)gjy)V{@Mt7wTo`H4W%ubSle069f~3p+N|;q>Qo7 zp5j3UeUsSWQy56z@A#ii7_R`;tp@+vKlh&Y6)wg&1NI$%<*7NF8K<%rO?g~d`~~xY zq(}$qR_*zi3-1DwLi@;LXf=^m?I9RBxM+OO)~vxnp0_}aP50Yk3FTq~FVh^Th1~x4 zX>nwk-dxy8VhQJRxA=;jl1U|FE;i|ZWF%#oIKUx<4&{A_cKON4QDLj^$aq6!kOs8%l&WPPtxlKfy7aTQx)9lfWzyZIB|5SId9%FYrYzSzrYTl;-U(b z&P>PmO5MW{KON^`FF>YeE^Jvrb}@ZH{p_WE9$&@gXS*b9Wzo`F?_Wus5r3v2;Nz6F zj>uenI))&iD>{=JD$+HFImSF-;PaR2Wo8=8>^#1wAEC~bQRD_=*R&0I{0=l`!P8yw z_*x5v_jhtDefAaWA@H*R4PC+b!AB4vRVXD?hm_uXCDEB0qkUYcR~hS2!0^Gma+%%^ zaF>DsW{Ta?3Z>&aFB;fzSHX$gXHDOGGF^J-=ADXr2zHrE)2b>z zUYiZfa`L?a!F#1~F^wrVVbBht5}Pg9nEjM^@_MJ-F&ioq?v1!wRD+wBQCZTO z*kENF1ATud*zIw0{>x_KtT$TcCX}op0VYzz@}wJfD=q^~=l zLW-QaARv!FLr0R|GmvYJh503hCm}lfh2w9Y<^+M^%ji^Q26Yw&jkQh1N+WM$36QKx zh))Cefm%)BSIY?l0E*1@KK~=jQ)i6Xro8+F(ffuvb&3rKBCv4m#tuD@NNjYw3_fXh z`fg)q*U4d`abs)XFyBCu$n~4x`bR$uJ%Hoj-jjm?F{gN^o=PtQVO=O8N-1EIwMNVJ<`&VonL)VkYUM% zR%mw`KJFV5`Dy-km?f_q7l{P>@(gk@sn+Bjq~4nC)NEFpDvyF9s@8dP&nT}vAAs&N zh_>Oo<9Fs1f_^{CAZE)DLd# zBiSUkt;^BN%0m&_^7qcouS!^xfh!q%S1=wR?I6_r90HZ?4 zSfl%fM7ZWa;7U}2^dLv)b_5H}i9tb(#&%g(3*QQP{4bVVvkYjuZk!XG?axI*xXwQM z+d7G-mQBzJw=3}?FwO8(X_!E$Z&8C}`ie(hT#ZjDg7XyD+Z+)l(Z)=|9pH)N$;t+# zj7rDnQW_hwmfi-9pc3FCIf4pA?5emp~VrCxM1c_7Ie)A4_&4I)6Fibb~M)?ZBi+I8Zzb+?L=v z-BPI}1~Ih3v|coYl%cl0_cX5uBS~u4MFK)Y@Pe#5TSg)xur;tN zvKx;Wue3@!FnJ+vG^I@+hdRUvwAykB_~syp`}dv#U#htZRhtax&9fAPW+Z_(r3Y2% zE}gDu;v=BolNG64hIM8B!0&;I*j!$zx`=)4QGe>6pf@5*U~xXafCAXxz_q$FR8Fu5 zUTb_6j&JdUA3(?h4xi|w?^9YZlN${|Dv>0YC%<|~B~_al>y5K*%vLlaAW~lhQW@BC zxW=(ReJ*`WhF_5m82yw~&9*QImNbVgjGkiOTn=SJU9CKS*&U(~jPIn)0q6B`QeOOL zdP!Hk!du17#rC0QP|rQ>a2*6CD+sqt>ikCuL*sikpG_#?Zbu6u@vUH)^f?|5$m!I; z4&oUrp)PkOCllouQCt4ar2uj@V0PR@6E)OL%Uw!76!FxmTx%*0dr}i!A3}n#9_(q& z@fv&Y)2cNTZBF#?-zMvsktC<_FQ&b-SZPuLRaONcSPAldQE0*dwwlcJPATX*ogQt; z(F+ZZx(tNCvq>GoIV5Bxg+(Jus|IQbP=nY5;>>+oWhSkRLj*gGSCp{Xh3Zd5Y@<9o z%7Wo!dYk>H5+si3t#!tL2DE8nP$sF6P@SEqB1MhZmx}>3mOtX&-7g^mbtBAs=iq`C zR5jYzE6>2A$9!!}cTI=*P1Od+vG~hoBbD+@tzD))kywy_u_=&>Av}rSh_oi%OMj%a zrZ}uM1gRmMv*Fp)502}ZM(WOH}a0a@e?z<(xk-j}$r^h`86WnRC5Y1guwF{IS zg5kZ=rbV?%*g_;UREJI(FeaAoK$3y00N;TVL=Bf%w;nG>ypwknVoGi`<)iSTLqx}W zPbrTl*~-mciAT32l=3viX5EMkpOi3cOe6DFD@u6A2ca-6C?W#YN|&q>3Fb9nm?vEG z^1Uas7hcx{HQ7+s>RsTZ7l0;{I z*Lt@&OWq$l0>Q|?Z>?!2Vw!`o>x+_kpGm&=X*ES;8|TF^CRh%VRFqmuI%%v}7c>RkaA2@(mbewwg96P+i_VRyg+%%27ql{{ zqMCv4(=wX0vemW#F(OM1!C=r5ps{F#(V5^6u1Pm0{yVm!Q%UrLG#C^tK)1!FbKO!3 zIXI$&?>(EGf1{x%aIOlS3Os}vMN*m~ zRo4&-|5?f7N(_fDV4FZRIv3P%6)IYc*f@~-@kdykaZfcbLxs;RgE2XiTPboS88sIv zQl@LT+L|#Z+LS-j=lN&d*kA!Tj3f@SLGwbmCgj!sX|2|&Y?jk)2iMT}FY7K7G7Y3k(W{*%Hn7VM9n5d#NB ziTN5Za6KzhGXcd_U{Xn^?TrpLWr`#Tg4swsIhcWE#CS~!-Ab7QS)jI+Ng`eTESwX8 zSNnltX~m(=j#FVePk`2`js)`*Se<8s>tQ*zqO9VzhJ3l?TDwC&V8~FVS%UUX^}e`A z{h)%ZIlpx#=vj~ovNy8`TI6uKC161a-g>+<)Q-$6DL@ps?fn1NH(4Y6mJ!L*`nt?0 zR?l(QIgh75xpKAzB>yK!V*Mmq0;J&D~&^~m=P@rj~}yXJSp z9HJCM?!9Mo;2NzHgMg`o-uMk{Ee&9bAhtKlTI0)#@ltMp2enLXqm+QB$QEb;DNn!0 z#M-{ge@pph-f%dK@##MO(+ny_7YC&!bs#mM-+iC9;Eo{5a?R7_Wl-zX%v=p&f6fd= zZS73=O7K}!#1Db`tf{6UBD*~%2;Nu}ATXS>dZiJWDdwtI84L{9*=^~-tLhzpiL|6E zfN*l}>27#PV!4$jILP?IXgp}LR|e@O6Jx#am1)5|F>#uz1ck8=h>o|(bbdCaG3r^x zbg#sOTH7=VaH?rTxs0*?X4ZQ-maZ&LB3?6cBu$-Mc$s*Ai-KIto1=*ASg|T;{4s`P z{7z+_(k~uTAtgu1FvxLfm<6=&+I%~0w&Q6b3YC?X{LWGZh|P2DoNu^Kd58Zu+K=M5 zDp#C3d_gtTdKB9anHwi02sQ%5SS)+US({52)Ts&ygSS$yvZGFGsu_1bPqY8IgS_k2 z9NC_`1%{>Yo7bgyZK#2{6bN6X7NW>XVqBSZHc;vRb9pn<>aA2Z1GG(&@;U5srpl&^ zj_+yyp4eVPC3}ffT?Fm;M1vcwXxNHK4Taq+Q8O3|Bb%G4D8~wgX3B9<_@$oPo?4<=n<}jGQGHk< zh|+td>?N~m`ff@{%^4n?x@_B+rNUA4kt8Kxk2fR^cz5U_ha{Nv0!)zR007jVU}(!D z5)n`1cQ%58Xilf}Sd>pRsPEtS4~#KBR}5kzTD@Kz0aA+4DPo!?+N?wG3+ICUGqF`t zM#RDy_jgJGs?T~?^vpr1b_RngM}?5d1xk%Vt(!-9uY?YjnNCBrAK=PHd4fiy$POXE z({RFde8bA05_@BA8wsS;yan8nzm+%R{x8d?hyAXUVY$a~iP1D_L+0*fUIq z2hR>-8Q!Orb$H!qFZN^u$jWLh4KYULfmq9W1xVa0m6>QnH4X@G=DR3{?^Y(eVe=EL zB@oc3yH_Imk<4248*0(qS*{$81!JNb42gr&mXrr@T*=IC&Q02KEz%H?R}vpDjm4+f z0Q%RLr%*xcS6FG<9w>{_Q#u2f!MlPQOL66>Ta`}o97V6PjjWcyfdW$mqirn*AP(qp zs?XrT@D?Z~m>>dJCX~Lov293HG8z{@Dfr1P_jelDz&)sXBSfo=;c?`hQ+{bQ+OS~% zLg?g8u)|6e)W7Ydz-HS~eW-n#>m-ra8uv^ax%Z5#V%b!9h+Q&Y1-;8doeZ&{g+*aG zkCgxmfx*zCfX*t44??3ufXadTF{O0tQY^doR0%4NFw9CizCPU~J_O8%L_h;~U{#gU z>e$LFY{sTBO%j5>GLf*BN}f?XIhIHFxMbh9GA6@}BMy`G0Urfb_ znqrqx5grRdgn+cFD6Kh`#g*g{HAfEpw`28Lq%VzaANX$D-3?4ZK&>AVE2Ki$HSS(=x$AKR`5$J78V z%yT8)dp4%fW@KZ@h@hy?h05Y0b0h3>;}TxH_Io8q9av3NG1;LmX{CccVj-Apg8b{i zqy)5q<4Fh4L8ZD3!4c&u3rQ+PBAMVO)G8w2qBP-ak0uc9H0_tV%o9YL|70} zCo@(&LzTYwER4^=1Bk=Mh8hb=C^M4J@r1A=tJJ-xYdWvh(V{rhJVshdKvRFlyFqRv zE}Nn--fh$68-k$HY?9A&o>N`xc7W)Dt&s+uG&8;@;!)LRee&Pf6-|Zm#sr@X8p0br zi6MRUN)rbZ^*EN0Vcu%&fdDNi)SGD{28T7&Gu{we3ezqAi6sj^XX00_j)t0}2ien< zt?FzD^u8gHSvL3YEC;|FWHpgT|0+{_2gtyoQKTQo8p>5ANBg9DdXPguV0j5aC^%VF zKaHf5FKko*wfgBQQ@xv0A_z8hI-3F4dFRHXkV*a&)cdqKo{2BdRL)pJRR=$E<6mTa zO%S@;A+gn!YePur;*CsA?NSOrZ4E3{q-V&$xa%(dGIF-PO^Glx{-?5i^sz^6G18}bXwk&b7Ng{}l%P~EWXB2pP^C=SDIX`eQV z9x(AA!7#pxEN7LIbR8Ya>46C?;cJF56~L+1vIE)<%DV5?*3%(hI|sH{IV8#C@6wy1 zRSVw3Pstf;Ru_|-*+y1O-aPX|{U)GD#|9Ro*6GG1lPbr$rEsZA)Erpuj}Qi=AqOfs zk$@y{$)m=hg^Ynp3ym(#BU~FUgAB1kIq9{+Qnp}*!!?_qMM-?cMxZkB4@ze5nGe?d zQ<_~P7%1!h_kBZFj*$nZE`%loz|4JmH<|8ARkU8{0)suS^cK`Y#jNHi8x1&;s1m(#ABF>U8P<&SgsB(+7ZQ;!b6Bm@Ln#xW`ip{YQXfkphgs*(5D7f z=-KK(P0H7OLrFTw9YGVjtW`^iRD+=*2rK~F=aH+FxAj%j|61_$mQ{;OwY5`{k)O#jYW&z{U(gfod^g#(2b9#T<(oQ@;&H;E_tXKK(IU zu3ATZHr6fn!f+1YPJ!}~%!iGyQ`!8r>=pjsSONQOU~1-4)noH$K~a4&wQ7|~rjE;|AXiGT#v z*I`Y+7{9Y*Nh`9mES)?&m?+MFf(Pj>lQNcwRbpIe@8Y71wxEl?pim!3<*8FRcp}Xb zXB!^b9=StUb(T2Zps8T0%_iSihFZ2gI>P8VJ?6e!bBhr2Nvj6NbZ$Iq+n{H1%opiC z8BFEE!7#2w_?ty7DIwXK#iw%S!&lcs4JkqP9%^%38P|sz$vW*HS?;O5Iml2MP#@&x zui*&pJtNn!Kzbx9%xb5=zzguaJ!}TzC>(E1jl2SCN?aIQJc)nJ`^7Wo!$S0tBKQF+ zKAY9OCvPYUO6kZT!&s#r0~QUIBF8WxVye(YSn}~bDYBrvN(i4-(|L{-JFmY+)>gU0 z<6i%GT2?#&LM4dk(90%?5po1Q4GcD2Qn(=K``)M3I>KOL0oY9FUiCk83Vg92i`);# z1%oVv!BYdeAe-fn<{|q+$Q_0`Q4DG>~6YcfiTS6!GveP3M1Lr-CI2l6>^n zL2&nX=4Y4tvZJU^)4K5Q2tc*9=S|;M@0lz$u0-&OkeSCi;YL_S`>UqpA%<3uSN=yX*!DpTiJ;5HBNPgF_? z^ggYX(=2|va_52}KQP9;G4+huuefN_i8RrVucB3sm||lGGO^Vau9a!|t?V>3yF5I5 zgPN>LbRt%18I1hOU!S`j+7ufU%Klpw(0y9swZ!3rvx>NnVJH|Xb%%56zxWMN`^GRY zH)7?N8|gRjIjFLTb*=t&TGTkI*I$OQFb1kYJ&{Pz#GrhwxtM0~(rRFz0`9qM1sTtN zpB4)MheP6mmv#WT9Tnok$c&s*lA4B#n6Y~$WdmJBo4020S2SkcL>W`4K(-)0%&w6; z1~##o1xnAuEk~oLrK{nKmU_2fBpUY>;L)w{71k2`uQLK?(g4UzW@5= zXF0=5lTe?M1pHtQpgDqP+zZ?>b+3mz|K*SU{Fnduw*MfDr0T*qVaBpoEeAB-Sbp>Ya{8Rt=`rF@L z|KwFvur;6bg?qpW!xdCWGW7|+uVBn7OX)*q{PS;p|LyPlq4<%Fz6N%lvX7F^;RhwE z6wJS(4B1>A1@J!~>Gk{P-}YbPIpn`@7L4shwo8uS*F!lOcgMmcFkyd|hu13627vKp zkC1RIoPc))i#Nc5&6q`4LO`Xl#eD6QXc4ron6~WesIz=+m4H2k z!<9_4igb#xsct3(RSyxG5KkZf?fYN<`rF@s{`TL$fB*F_uRs6#yT$$cm)HFY_)Lu> z%&YiJ+M^ypKo%YB%gDmE)Qm8(=l&4vG2g!2u8?*5Rc#KmCKyJa0TRt{CG6Qu?7eao zEdmCGj3mZ*I#C1W#$Uzu!EHhIxBv1(zP$YQ8)EOj{$yD+uQ}TVC{|Sub;w`MzUr8y zMo_c38lv<5-~RbxUcY_&>$ji3zkL4fPu>j#q2}n6F3?gTF8+&&#ysTj;j{p<)Z0va z|MB|!>$jKhuU~&|Oa0HAfBD<5KmY#nm)AdeV5Z5;a6!HhdFD z#{d8G|Lc+eJv}K4>M#H6asKt!?j>eXJFbC&QWtx+z{XZ98vtxGBvcW~ou>DWDuUiFvBZlq zu37hl?MihbPv0$b9$hoT8i%r1Z{l6XCLN{VUa*DsXjE@^ii16Wmbcv@0H3g&IrXO2b_g3 z1Z1vK8<5+eq-FtFbSn<&SB+WVdqiy;_h1RB^CE+~CrYp%{zh`x3u zj{Dg)CNDmiJ`!eNM+^cYO~|xkwH*j~aD%<9Y}PHVIYoJ^1RJqW{eZUm5?zDjQA-9+ zbOkBO=)g@DMV5((_YLu`VmoLf5TYyab)g$nAIA+{2T^8rwv~Oa+Da#pNl2(3Bahr}v(K9)el(vDC>*4WM?Iq7ocDws0BlD-*wtdwNzpFZT%mk?xg) zk=|n=01>jdyP~?6qY-|$NEC8<;k;7u5U_Uo3yw+;pBJkdE&+|FRi_|$jPw^C)hgPG zIrOC}-eCa-w6ic(3inEx!lLFFF_b0sLs*~(P&VtNHk97HOc?mx2*+RnK9-o!F>k&& z%L5`t`)3Qo6dca%Orc}-@k7{um`SBF{zId*cWg06jo3Xo*qMUAjGe8_v{9NK+xW8F zAh-#vCxHh`#Rp;qR`QSE$&f|-cRWt#JU~Tyb(x=pg{Kp~A1e;7T}E`k6jQO##rO`a z4`rM-YQrEQX;Gc!td<$~#F+2`ek?SZD&eia>ye|J@aig7!sLn9aF`9`O z)ReAfhxlI#8H>JIg6mugjsb)M8zCjeM)qJ{Z=)mR3EDVrE9>i;v(tMA`4kke2@i*5 z#Cf0)MUSaZJ&}nxcEkOhh76fn2_j+Yxb4hc1GRkANJnN^=2|ooQS4rcq+_>iejgtQ zUzL2KQbrbVzTm*U8$cAExmThfL@)5KITPq4e7A-pnUimqqDz&xhA{j0%H|4MoA{O- z08gYt2n$*vX{ZFg5&zbtFtggE4{A8pjfIXgwF;845K+WYgmppJV@U5k8!kpJW2fMz zG1yF7x{t}dAehJuPhXT&H`Xn%2p?cHkg0G*6pcV&iF3*T{!EUsrEA4YU`TrnAxb3v zbGV$z03pIfB{jZnkCH>@EN`b|uy2u^Y-Xbl-2jO}%}SWowq*jQ0S+q;`C{G7$_8Pc zHY7p>@!tREoA^qW20C`EA^$E5q1U9o*Qjdf4k;d4WSptAsZpJ&4y$wDt#Uvo4_ZJ^ zi8WxUYw5GyOn)`#0zT{nP2$A8k`>mE6r|BnChQQfh$WI{Q&HU$hsn#1Q@mHkt)nxn zR%-@i6X|>#m2>ifma#@_2%_VA`jAEsH6aC^u88yFyb<6ZOkpmg6d2PzyKEH>7-B0_ z;OL(>;)W5x;vxCz8|$PLQE9v(LWQMC8^N3s>4?#FS%Z;feqNHeo<TTANCRQmj?_gj7L2tsmq2t?6<$~*AUY_2(_g3#YUbk8U1+G!epVuIBZn7Fz~TVc~#Ep(}&v77$Bn?Y3$ zH3$5OA2k>d=>6HHiCfjcaD)aZ-ih@FVzI?Ib+0)CFbARu{9G7}@FTIXXCc`fVnJ}r z*ib8%e-9QZ7Il9OLPoJn$P+{)A8^4gb|`h#p{ibLsqXNpmh*&Twx{usUau>!I5o0`P?T-St8T6aHcee6cFMhXXWK%w7-7*zugr&KKn{ea z?OuMA#}Gz~v34XhI4*=RykNb{^=TG9l~vp^u|k^@FUCD%riDz zu`@FDJvOy$L{0gQS)@X|o^lbB8ljX>NO%A-fk@;9wsVCIQJ2 zTi$OnGG-hVJD%33lzg-fqvLEwQOsYm=5zxSfwL@7)=w`7EHF?s2cI->cz_^f`eQO=Kx} zQuV#i(hM#6iU({wtsSW{YP>bQ+1ge0SQa@XvK_MlL{(Al?mbal4aNcaJ3jLF&-KUs04Pn8@6 zLkGKdi|CKi(zud!g3du?v$`8S9Y@l60Dw6T!t*z21ERXW6O-t1yavcIgEZWr;~IXH zFJw4z@rcaFq7JN}Q;bQ8 ze4Cny9Vj}_uTnAyAO;Rv;K~gdx0Bx`Yh5Mv4ys^CYwL`N@I+K~HFhF)HI8?Qv<8o} zz{Rf$zxQeN?m~>5R)j~+N8?1Di(|Ny6x<2AaZDuRm3b{MHNP4Tih#CI1LP4s44o(` z)^knhCI*@BJs~#W6kXfV3N~U5a=OioWCu?i?=DDKMysE*vHF+`^`MFo5D_}CeLx)P zH1%K0#PbaIp1>N{wLcEfip*|^A9QcQg_SC+sZ~vUxK}py5vi;c^1*#vkr%LGgjIXU zYrL|uKlS2XiFHalwb7JSJk^A+XZDvD$LJtO`7AY;+$)(B_zd_UC_+klbqjW>g~N-^ z$VOImR0*o-UYWTGx5N|O904dpBLXg1L531@C@BL?Ll#E7g+zT06q`Owy~8=|Ek9*w zJ%b$psVfG&8u(Uz=lte#0Bsl~ILplXOsgW;;}i7M`HSA+eOh%iy~)Q=p<^YUgA0IS zG@%%%rv@d!pR}e%M=2r6Pzllb5<_E!kS_v0yIo;Z#f84U_q1?Kk$DbHwtj?Y2%+g& zn(Y;5_+OQ?=G_rB)ts*5CKQbf$&THj;-RKYtM*P!UCo!f)-9K=-Kc*-(?7cwB;2tJ z9T)?6AtSVGAPE+ZSLo_menwxaqVhOpU{kHC_)T4RQ`M*f@%~P1pYX@(+Z)bp>;pZ3w3R;Tw?`02BNbS4Jfui|(aX^vpo%ol8EVvBBR<(ytb5a0L*Fk#LZQ%?#0x$>HJ^an3h z7Y*n}rK?|w3cNbC!;(tK2BY>b8u)xFS z)ha>YZdK`UB>UIB61?!aard>#Bej{rcqW?*FMrzg*lyTkvFb3F!ofe}Wy3$H8eK>L z&)vcSB^r0A;`cnPBK2Vahko^(1*3*X3W|O-B=JDm+V0?j zfaZH!%s8AQi#O5Eqj0qcrLSzqj8|qHcwH?zT*PB=MYG?pg7F{&z_a#)swMlmJx=I} z3)P}W`PGWLxYQ?A0@XH_nq2;URLtNeID?xPRVpObJ$3$Jl8Cvb+C+QFRXbFXZYi-hY{I4BTqkNxsD6xlpx z+BV1lwWEv`FM{!uSbL&n#wHF2UH*(5ByEV9N(Yt`sqZ~&38sY;n)JMKB|G35uS9;7 z^_#gZ8lu%-#u^d;d{Bo!@_uz-0VPmmDH)-(I@GCiSU8=>J>_YNMPtVNG|LXsU< z$C9eP|3 z1a*-gTDbSL83AwU9PcW4HP}<{G$GOW7?i&Lb{kv?ey@bM%|%kq$6~RfN@vg<93%(h zxy|Wx)*x2vUa2sKXUyF)OaOM%o(|UlPijNSg({%)OXJR{SNO*}z@0(`G3hj|^9P&@ z(yM0uXXL$x0N#7*Ku`6ogBcy|M3?j~8>Q1Ny(@sO>SrCW%1Cesw=p6Zv60THVG9-qsj>>?NI)Lj zcG8OIndfh;TZDOJhSYFI zwudC4-8%G0clUR4?Wi3AXlP#@#@+;R=!NhHY&Eo>^Of|7j+|{rsG=C;#dwg-cntqf z8nLj_xCbQ@n!*de_jF3OJ48kkzEC<$+0MHHP=QZ*K|#XUOLn_g>Iv6J1y(c3gz0lM z>T~lUeN>=KM6f}O%9|aG&YRj zfKN+)=iXBw<%+n!lP}46AuPD5?HXV}Qi(PuOm6XCc}a+db%u*NKAWW(H!NJ#NFy}l zFX5+#T@`+S5IHyYiZ*q=bKlaoG|$SgLPPRt?}_(J*iAa&;9KVy0NS8@W5c~dtSzp` z7;lI{MFF0|H6}1w758_lt(ohX7*&!SN(jeJMb}p@^)(B@m+Fk)wc?*y$@S=v9oQRy zOzeeMDi-qVz0&wn{GMIK-Vbwj zN)9ZiQqy^2l#&^4!jUTTUP;0&x(V%d_UrMXBkARWXYk@34CVe!n!Q&7#l$?OH;x@9 zKkc$;Eo?6fZqT1zn|*kl)GLZm<_xX3Rv=;bROC03lmP%eZmT2gbvfO8LIqj(wE^Ju z8heU|Z?zh+jw&Tjo2?;YjCI?XBI{gzl4}@8N1G*PB^Z;NX$K%&Q-AI~dD1h9W~Yc$ z$RJ@*itvNifK&Yzbs`zyUKwM>IoSwB3ynebIdDx5Z2CxTq?V!pIIh$Z0Zgb)d^tU! zV5arWnEIl=VW)Y^;DBXaD?Sa6CTq>p9GrB6@8|RbGy#@uLDs&|yJoI(5NH6vSoxBG zZC$>$30jQ!D=d)hh>M)V_jfX97}yadNW02oP*+)!4g@G7)!Ap;uhZ~q+j!Jm?@!?= zN`sG@U7GL7AB!uLB{US=d-BPm8u&G=8g@6VWJDa9nJEFrL1!5PG0!L@A7^~7xgW3) z!3>G3#oNROf}sfLn}9=O4dq(wh#NSKAIRA=0C}S1@> z%YrH$e2X{+!)c&J0eg20;rB{AQ1WCNQTzvQsp$3JL9B5YwtSBw<%AO?=0=mp;^m3k@zBMZp@ zoL^b`bW@U64Bcciz#&GbypCw19fFmD#W%OVa!1E47p!0CV}OcOsXx~Ky=NvBi9vCN zac%4%f~cXL)*!?vD%mKkOyiz;h#8Np1d*(&$U>%@!bCs~`2-j*M~&av8E~rl$Bw8J zHK)8&$e8v}lNM95J!i+Nk@$;sslgdVtR}eAejIhg=LbBHN9raf?u^)neHWZPE!qJg zDC_oqEq;K;iU#PJT-4C_4WY`&{B&phIWits+(}?H!#|ZoJIPb5HH5$*%{q1x2M&{Z zrL6Y)g^W3oc)8V^)Q5n+_rzfsI0rVW>n$gNrQTU0kjNmznJz;KvCZz4O?*_C;K4f> z5ewzD5)_yQvh?dRi^f3E-787DVZ*BU_oYmwXdwc{Z(fS65jSGxHhF*~40EuVcE_UHRX=!wxYrwWAml83g$JJq7b2 zny3j7T#e&;fy5^?o+hv0ip|Ox4!8Bl{XQEan0KlB_ZJn7MTfkhVgr<_6jBNO+%Cj^ zT?uFj1VesAnEAJ0x>h+U)NJAr3fD8z+PEJ#|CIFcxqo`l1 zZ@^08_WDWPcvko13{7AAmd0;jlWCMKU3zFzA~oUqNf05-1VECyZG+or|?sSIGS|Nf%ChORtvhkjtRV0J=PYtj=6dRngwC`U|ez!K-=O zlLW25sk;J&t7zD!!8aL{c*E?gd?S?9OQL%KS`W1*E>?rCYoMCdZdxy%Cm2;xBFZWS zVWe(ic%J|c*)O70RW3sab4>^;O*GF*&U4Q(e!gBU5W&88BfLd$q%d*KbqG61TnFLIsca!{iW-or?ml=yJF2B(afa`MSBbdmCmVjxoEJTkNsl1O7tt{wnMClj zGDZ+~IJrS~ih+Lx-8NOb&8I6ba;>I2^V3FxjRDB}>u*B1x9Z)JPerLysIWjGAs4q( zWqGJ*JBHO~3Tz9rFo(8vb0nGhfS8GT6GJ!3>9)DHR2&1Yl81 zLPivnJ47&Rbn6>9l0!inPkxp%TqH?6;^jHRHht_Jm$U{W>^w&T0t8UQsX9N>C}H3Ww5JTCtqVWvZhgx0J!M3UM! zsex5yZ2{Y@B+iq>v$k+2>EMy4%PfRF`M zFHcM&409$!9glSPa8W{Xpjy)_Y*Mxpij`muu(0pxAJ*TLAmgcA6*Z7|$eYovMg9V8 zyjN(jNtS6+pEwFMrv}&B(hQ*r(lu)FfJA;&->fWFISjw4Z>=38PKAkX2YtwaL=f^d zmRYxhU$%AEPa1#&MM?@fX8bb8N(l{$9`~hV(sRM6h~hve>c3a0b7VHNFgzz2EsgjnI^5+aGw+gYAIzwYDbWd3RBipUE%5| z3>(7?(-`hE`zq0+^A&gk<=#;#lJX8z3ekdo=xe8_sTQ^|)p}%;xunFMikggA(&?jv z2oBWv!~TS|q@;q`87fH=(NO?L(4h`zv6}R!QB=bdBHO0|^+7bU_$MXU)KC{UpfAW8 zisWm|+1hbwOI*(5m$=1|b}kiD2qF(-m%4QXQ!dj@<@E)_wvRBnk1ZkTz6q5r@gDJ=p3l{)QKpYf1#01e)iRXHBkWE+Zl7F;i2xvJ=+q-eKRp%OP$Zbkk0o!&MK1t0EU!%TNj=CifhJ%1`kim#ZgvQS#}5@QIQ-#_a&T8 zC{K|QL!sq0ODQBkd5ADnfi#K`?w#-Z!`%pO&+C3B+L3D1%9HD@?TkrpW7(V{rV zS!vnNQisvNcJNke7-~y=Ku4>?ycOnPkjF!I5E82-bVjmZ2f1<_34qZ=(5kx14BZRW-taOxsngj0qP7IW2uToYG zZH(%AwVEsCkHdV7=;>G8l@u>8L8pm!g+!IT(q*_>fCbzY!ef<+_27sM;E7(edMZKm zK^FLvJAh_SlYyo*Vi7TmnA4&}N1FuKOOhaUe&E2q7kJPEpoh+&%?6ONLVpH20W=5- z9vXOI$SdZR`BlYObVx%({$35y2i2XLExeN`VmyB%oK#ruFtN;{<#~I{a>unPPq(u$ z)aKLa-I@c0E_sK2rk2qmLFwdN2nfNSDnj^IK@L4qVq%@Kz}Gmx;7O>!6f~R8Y*Vui zBSV-@UUuTCnNKaaF2Ih`uq&v{`auROp(Y@Z zSF0m$P4j<)+Agc zn+^7XI+$Uv$RuD>LyhRQW~dB)(V%Obbgd~l%rdLYhO2dBf+IsNWg-T(Q)_K?9W?jl z=DhO>bqHQ(fYoNR15W(87AT76Nt|LZJ`?46Ik%&v=W(#i1C)4TNk8D+Q~d z57#n?=<^dMB`GJ5Lvbb$$hc3x(BWOIG-%=C_iVMPPB4z6LZtfe1^$azCSl4cF0fx$ zT$s7US*%xEBd>$^t%?z<;pW-RDS{OU-x}gf2t;qQe$vvy2Q_hwZhGO4XuIH}aWWo} z{fU&!Cv*LzjosdWx?ovg0{nNmCL}P>;A>c=XXK9m@tMeT|FLY!iPK#j}^ei&7Fo#Ma~{xBImB7 z0#tlpnDOkxx+MdE$XKZ=1yzbrq!dm>s73Hjx7CG5TUsBr_1qhgY?J(hjlDXao$=v0t|0 zp{GKuT6Y^Zl9XT><4l{SN(-j1(4Yt;c3BOMZW}rg_LdEsD7XG*j{%-z_7|(y!G>hp zrW=K?&f-(g(`}~DUO!1CTbL0E;BOjExzO78I}!}2!E6*HM1`|{vS~Z8+2)vFC#ubJ zkuk~a$PlF_H& zHYSOR%k-ftGy+d`43t@aQ$cC8r8Xe*D7A?yNHM3tXXqp9!O3Mj6Hk+oXT0g0$AS~MQfEtfUNTgCGV+wkVbM6GbVLB_dUdHXn%Bo<5 z3~1C^bwr4gMylV8r^#!Ey+Y|p#)z{dXh>sX1VHcVsgMi{G%YBc?06EWHrMqD$MkN9 zwP^c{>2SkY(^8}f5q8gdpV&Q0+Z-Pa=TIVcPMosFN1Qy`+E-R(IkJ9I#T5J4$hqk? zouR=wT^;!*l-19wa1`Xz)zW%7Zw7e--~l{1jyrbwph6W`+`P6|;X0i#oye}4B^eKh z#`;g_5%^TeJ@5otYSf#k@4a-7ot>0b zE4yfxZRds^pvL+Toes!n{Y@hsaYz!a7QG0Xen<0ST~Rp|_UUA#iuIEW6Ff?`4K%ob zFUlAgrI#D}V1bwx0_M|huAgi~S}HIKn>;w=1+9s*(g+K?QU&ECkamcD3U=**OUCO$ z3~MEggIZL^rc9tvXN%Khdh2gWRN_~jA(R_nK^=-L#=|2M(=2veF$uts?JJrs*3T37 z0$hknJ>kmtEd4NrAe4JFq7mP`cXOz zpCs$*crJCdj=UwMC8L1?pIOrWNchjFW&I?kGtp|@iIoQHBZo=IFMrYiyG>zAzdrTa zo6RY!-OkgaNu%jAM`;H%p$MjukyKXG?q+bQR8Skz!OOU?Ap+}d4li9;%5wj~pRQL+ zEbFapHo2-01_Y`ODXqhw;RUW!Ys=yI@WZva`?JNF#yh?^v1Mt?E^5e^F%5-s77tpk&A+71{+39qr9OwAVl!1jw&>V z2t34M{Y_W{R6fIb42Th#rW15|0#<@zQ46+gPYzqjNkkrm-VL1s_LmNP6JFi$FfJOd zmjGOaG5w}C!81^eDofp-Pe6SEM-eXq0u_<+@WqGA`uF2Ehj0IL)KwF4xrAoP@W!YD z^p%8q55i#umDk35N`bXrlo%9$Pq_=36dX7mYG8bacWve@=Qv=)bc!FuE=EZX)&}S% zR1SM8k~@V$PLmquIXQCU$~D<__Y}wWo7Tq+YKcMGH&x6o!iieEV2<8iTpe889t+Vw z{cw8z_K!L9m|IZOZzC6`v14m24gfW3AJt2HK|i6C8)_e9rO={Lvs&>gGzsW=wa7N$ zC>KN^DpiM#<_PQ8QZ7KuiPV>0MoPo`jA>h5sV7D|R*5>u)*+2|U0FMc}~y*krzl-h^&P zk!lu2!`acL>nFFAs?^TQ-b@8~Vhx*e2!%*?f|C==#%cBJD@7tLJK?j36=qchn&)WDy^eOvA;R^%{`hu@@ zN~H(p|4;RR%q*hDtYeQEdJ+6Vhp@*G9dVa9Gee8Xxy;OjU`5D==&s_Y2B8!|_oTiu z`7O$0cBU1N7OF7__m&5Ba8L-WO)VK(CTm4v5%kFVgmeT<1H1+zM_I+JE>_gU6wgq@ z)mDi5$8H+>p>5Eb{8{-x|K{j3Cg-BU9Zmp<`;gjWrYKDKUmPRJeuxko^c)vXFX05OL3j z$r!P~col3j^CSJpd~_X1aXFNN3U{dO6qHfR7PK1Q-LB8J5?L33pvVy4%WdvhvM`~j z1x0hnzZS}{S2&&vRB9pvyc%zqpmgnK_j;+r#_xOwrHHVIvCoSIG4; zW%fpj9TWiK6oWLm0hfaJl1xQ`2{)*Fc096W*n?qAMgd~ckk2dYmD@2!VtqE-B-*MP z%h+A|1DJwY_92(0l+EL4^9Q$>R71ECc~P+g@)d(rXS8qYTQO09=h1lB6i^L3tLM_t z6JibEW`hb6l+NoXjipFHrsAQzI&c|G6ePJ%{}E(wK3ZQ z&L?cfpXc7jBw)h(Nrlu(0;gr9#Hi0uJo+-;V_8!YbKPAASr4_ibQiSlM0a1S`VPAHpagd)QWMNn%@D;9k@gL!JZRxRW0|=!=De&U+%7+ z9v;2=$l+EXfJ7b|dH}vPl@%gQu9OT*18{5tQ^|e32c~p+xF%Mz=d_;wtL2<%3o5Q- zHnK0teZH?Tk1o#6F3u0$TwI-gth&T(D}baf9kcF4vy4C?d3I$gDu7pn@N_B=;5$PF zCsP1x3`fC9z077?;T!}l=G=|OPYj%HPj5|-8pf`qqj)N5k$PiqgiOL-HRCiPY;n@5 zr012T75IrAeFNiL(YMZvB2sR?H}rta{2@E0+XuGpAyrt)CEZ7^GJK76tR zE!>_Jo`js%rE?#)kVH^!EDn<|xFY%j>J_wCr>x;{)gTuFfJhB7dyxv{!j{ow>{5K9xPR~yd zwqqa-YZZ_1T+0POcB~hvtrW(tp|h^FKa6 zc>3evzjjZ5d-nBHR}k9z^*7)DCHU2h+tz3v{O=#0JUKWx|9$&f$LF0Lo*%ySe=n~e z`FEf0|J}*q^^4QQ6uY-giF!>R{jRpL<3} zhga7JZ=YOkzxMd^)AN&ygR9g3-d&s=9KX4~?C(F>|LKc&*Smv@OHcja#pz2=>G<^U zeEaubZJ&DZ?_Zx@U%Sxzzw+hwHx6H%Zh!yr(OFOB=85!lzP>zq)wS&(#&$E?lRtQK z=rtT%?oNLC{rP7H{g>?fZlzCsSU(`}t-8KfO8E(HA|q&HtCbzTLrJ58UQy&!n%u z{nD>rcl`(7{`~ci&kvse{n^36x`8o~j#+_Cb9;2!mV{24S3ipHqiAN}PysMe9UA~| z$^nX~l&S(#IDqC&pcqn0iw0*=6Ar&w6qGHvh2RmZGJqqB9_c%}HfKcogeln(MndV; zZGiA3gs@wetPL>_#aO{KicDglZv9PORgSLcJU5CMtb~Rbft1P~!zN3r5JkGlu0~E( zobWc)6AI;|V9o+Y4spUzUT$Y{V+N;#?EL*sqmFR`1j7vlSh)x;4Z?#0c7D9;9zuB* z%e;~D>@&)v71>S^POYTwfa}mYsh>tct0KXGS012bPUK{~mleO*Y|?=os3iwY!2sXn zqNIzZ*l2uV%atmCwDj~!u0_FPKzwTQFy9n!zzj+X&9Fg5nNh2x@HCjKpim*ApNDdw z+f;S>=0evXjJB={fPq?&~L& z0J=a@nB;)vJPK$I56EfrK)~}rY@FB7l6JhCqCR=Kwh6{T2x;5Upc{e?@xe#XybMjA z=?h?qw+d(IxSq_`$bK!VXco>nF&?DY>(#15eH7abyZPMTMcFo|Z4;1T!1pu_c+_b` zvDQW10Br9h8w6})E+}ncST?~(Ak!k6)uGh-gnN#Od%bpi2pdT`4d02Ez%&{m2~uvB zbiGUy>edb~e6XO?i46V-1UB}WHH4TF6O*bV! z1YrHmFH{dv^CY4QkverWndZ%xX$oMjUM5O(h(ikJ9JLhvfQg*uwnH#9shQeppUrz~ zO{d0E?b|LR;^sPUa!IjeoYJu>?$Z2qO5%ezsJlmF!-o0R8>u2cfdx#1qx6_0Ef7hwX}qSLZ_a}Tk;;W! zo937%Fdbph@K#`9YHedC%4~vJ4+{|N6~pcn(EbfRO_e#sgl4Z%%f(DEE@>%ty2Et^ zEp@m|gE$?49gEc2xLMVg&am&Ni>PxA;2!M9!dWqAJHOE) z;?EMQ3!$Zwn53`>JMaZLtl&(o)Nxu4-=MRXP01_t-gKA1sAj(8{_>8qY*;bFDc3MB{dR_}%WN1b%wqDE9Rg z!x0TGVoXc|Vv1b3s!R#!#lmwS9*Ib$uJx19BiM_n&n5=h<-s@bG>0KVtCn+&L{q!e z$TE$7n4WgL0~x1CNzdrKyf_wEZxVAk%(f(hRgoyd0LTi}sWzZ*NE9b_G!@bD7otgZ zCNT{Z*Oa&-$p2bjm`6&wrD*Yxh&NR25M5nA*BBX34mBg$YF$4GN`;_MafbWaEMlYu zYg~zf&9Uqxf?!n}cm1Rq&oD#;m-y0|2O+bmAIEqVAT@3e$mNmxM8BCdCRg@}5q<4weMzETs6ybTeTCzj(Iq6^NCG*Z@g1_n$rR%I${8vn!-!6jqeK)&)(x%dcp{14hbS;ft z0-kL;e`s!xEZT0CCe}|5Z#NsoL@1#I{O)@@I<(ADt1J3lRGV{MW0^oIpDx|3M}V zW{KpV7|>UJ0jpD^GEODBneNkb5J;s|g^Ui-+@%!+8qL20xSCats5yKxDE~3$i3Ec5 zE877?tri6QX|hda5|^$F>`H77YqcOs#ti~0!LM^{g8sCrYFW(qT`c$7U21w}!& zV5W8{$4C`TXM<${i8g1d3v|+l*v?%7cyxHZ`{Q)TNfuS*5t#)EKyqt?u9)^_c{J1H z0gdL2UhAT*f`MZ%o>COZypoQ~_K|0W0`Ycl`M*90(mb3KHWzyPCe60Uh$tf~ zp2T%(dkYvI2DaCTRDn>64T|ZhOkOtf$o zf||HlU<<^tAteGDd0I1zYm&xfYwOiH9J&g-Z@teb=tl3!DpfAJQvg2oLX5Il>s%o+ zzgorhlU{;JAv(}%l#x~|Klzcehcq_5>929|Tj zjeM-1OzXl%jHL4mYAHCCbk}}>I$Z3ExOIp$|g9+Ye2a01!5?s~N;Yi%T}M@gtj)VxRNXNp75s-S?I z!(WHh*CAFAajPDLEC2!JWESwAh)-Sv0A=v3#9e>W4wm-;$&8&gEOn!a;iRlQ)FF1c z1!bmi{iNLtEF|v@VMPzeCaNa2r?4$Rj@uw>n5Ib$%Nmr_V2~kaZfsrx#!4sRV$>!; z{{G9vCTd->)f5*KX4tBI%(|h_W(Kjz;#xA($x2$a?~o3X6{4~Qotz8d}H%LM(JnQC&b_c3ge|C>b0sOK%U2Wp%X@_faGl* z|KtTiQR1nSbDFz>$qNG1^xl5%D+QF59ibYB}J&2(vzv% z5$eO$!l*IPEE)|4xH*lYWrwcR>Ka+1hmyc^8eQ*GhVvszG@03yg_BEQ!A31Os9Ie* z_13h4XZ<7;Oi6EUol<1WCq$sheW6Y2@$@DdLSw1JC!MDe_DH{~nNgRN6kVnAq10%Q zqZ2|WrKBBf&QGOff||J`&cnnuz-TMl{ZsmHR(Hd1!WJbfE=?jBZ8GSvrOVmWxexwH z7&fa+Pqbbw6**EMD`i+|Geg<$Bk{9KAtnLt&u^^RI_!vLX5iA_vndV>35yg0++4yi}bD z9LP?Ze9sNTXA#$^0YgSg2o(y44-OY5#R)F6pP(uoo~?JNZOM<#(GUtSFWvAClf2F; zfeMR$ow7I7U<9rjCDjE|0@#$C5)+6L>h%~@6BMzO}K0B5LTGk`4( zoA%LoSX%-DjCTDbcq>;;-6Vd7>YLM$c7`Bd2*Nf16hQ&wS+<`Do6Xl*W6CbIu9D5@ zq5Q-V74#sxTAR}xs%!;b70PwMnc7NYCM%RSywJhZc@4>Rqkk9er9>@z1rbmlnhIoR zBp4AB0|ktmMX(Jq9?fG40?_EZ5%ebtCIm);(YB%zwr2}6AJz~g#qcSwo+cutS|Bu~ zp*C0aGpx+E$4$sy!o^_LI+72gGvwqWQ2AQZTy{eKS-aEuK`) zP<`eN9Zy6ziK3KV)~oe8&_MbGcyi#mN3E3=UJxgu8URUIWKFK0G)vPf!1)o^n(BnC z(cW6YS_m%|`=g16*{+`ip;my2$Z!zNkY>U3BTOw%RZmA%rOlkqM)%_tmh=;M;Jn`0z&1qqXG&BnVRk#hBh55usEW@(O$lcS{j3l*7A~ zJ^}<627NcMq%&)989J!-J~@|*&rKR>{TeYgSPqJ@cKoQ)&6Wf3O}f2&U78?j;9_g( z@h8%*N;$9hqBO@KduaVl*_>`m-6#Jl2_RMiQeGW`yc!a|rZ7Sit)Dc;tJO3_FRio7 ztRjV>$JiA$8En`Os*V93VT_^dW2J)7)G!l0<1RqNQUk)m%KC(=C>Qm8?qqL7M4)YXSN%h zY(sWvOjN_h=NDRwI}hEFyGhM7@n;wS|m*~O5FC@8Z@r+Q)1V2&T_`^;n=FKB?! z>(m!A)G(lh&6I(g>ardrwoiu6)xjA`AsA$0UdzFM3?dt{k$=exWft>(0FCuG10fCE z($8}ZQFbMf(@7$f8paO~RQ|)I)=$IuSejL7<%R*cfZOPBZTH!XzMV1Nlg1fo-4fa<~v)=wsg zXf_}!+`dlFoSH}&;2Xx)z9B=8aOgkv8k{*~CYQ9MwZ3sgHC^=*ZBng_Vw65UXM$jft1;JM4 z>-q>Usou%U$){8%z_8|4#3+@@)VYeyz&*90y50OzoQPXQ93iIc0#HH@V3YH-D$SZ# z*yx~0G>Jj|51?~1EkM8k-(=PawbN#Dtox)GuqlCHHgbdUTh0Wqcr`$qZ8}NWLN!1^ zR7yGnQmzgrgYDE0yejsN^qLw4S@`<&MKs;zH6h#K_36<8+Cte(pTT@z3t>4VMUh8U z)_NRsgq-&T-1U=mv)F&I3E@Dd0<^^-yPWcfG9e5IVCmw?uo46e3+C^XsPczujlNQ?g$>=v-O(2+3pV22vB6efJ zH>NvGGxd6)*(?60de6VHz2*QzXh%4=L42D#Q{2mUJY#z*h(= zz(bi-DQL=trfs76zynxrI$Sh zUIuJ4N>mWG3r4wMXHn2z=N->}-^r5}BQ)ZPw!FhYO$1wl6ER_KOLWhD4L6RFfb2lK z=?|3h9<9e5w>ou^OsN8GiIrTItV?J?f3FxM9DpBJIW$^0WL~eIti%3yp)g7ivZUZO z$SP`Zh?IKF`yQeu%0_CJs)3uG`OE4-rw@@O&jx&>e6sAD{Mfwy26VNKPPQYzr0cRJ z#cyhoh+K6!xMAwcs3IiWIJDS8Bn2U71+9`-)hY916m|bYF6(4Z(OOCE$UW$BUSoh3 zU^hU+X`s#ZfJUa6fQv*F==>$hPpmjL(;ZK%re|nE`e*g!aKElTd6Q=iMca1D2S7m2 z0H{|V6c(!U54LC(S%?VNsvtjHYhfeiV2H`|P<_D)!Q|&UEOR%Jzrj+S{<*EI@Vq=B$f@rbjYRzp;wbUDK_ZoXcgB_<}l9nOVSU1 zgV^O$trFMpQVm%}&;+Y~h}3E!OfjiyuCT~IqzIj?YM2$1no&L1841(*^Hw^3g4ver z4!%Owd%mi7M2GDun<>ahxQ4Dn))frEYgz!t2^(bG(FO*QauL=_QW$BV*ZV|j<;MUl zvywtkvBvOI_<%kpVN`}OTFmv6`fGPXI7ehgd6xuNV{Hr_ovJ0+j-580F(AJREudbf zI5nt6=oio8ytmd6F_)pwT%@M=sqKX3cnS@#BIKc4S;cl4PJy$NbePU^8m_kKKjB{~ zr5Ws8ZHrAqFZb6BquAAg8i3&mWfa#8cV1r<%5{zH?#ehsbd9^rq7Yo45G+UDf-M_q zq?_>mwG15Z)NFu|_+|X4ytsan$U#7m8K{4#cM@o;rWJm0Sw^qG5s?K_yM7Xn4lf`z z)jFW)0Gv3P2D|ZnG4>Q~cIR}e6d2v(wel!cohnR|j^Y9PgNJNUvIpaTln_H4@&HtB zo-czkJt6S{n$Ge^tw^~O2cL@RdS|qZK~I>WTgEO;tB;bP z^AZ#Y{GRl>UwSc^8h9owMO$UhbI%#-1dG)B&@P}!+?D*Nq=Eu_%ibs74Bk7)o82Z% z`}T4g&{x5yV-s{iZ#J5NX*;b3$snY6%Q~Bd)6}YKFyQ#;PE!p)&43UC-swhxaEW_U zH;up_1VN^?o%#K1UbY;s(Xr=1iDnK2Q|i8{ET_c+3|Mo-e&U$=KojbXSV1P`jq1~f zx{zVJG3v_Q&kzN2M+YFL0dfV1IuB%|-<4p#7yxTlPV`L8Uz3>D6l#UBD~1ih?e371 zWv&}jKBPU>iAO`mV44=@M-K{}*oC&Oqm(=$c*>-sYiX()lX z0uMZGw@r&?X100y&`G(JgP8|TId8Yg zVl{@cJQ`i{6ad8rm7-IIj z1CnaXga^SQZAzibFhinJP&^qyfT@TD&lrM(U=8|fSd?pEN_d@UN@6~;q#)2Vi%_*c z6l0Fl$Z@?X(s=Pkc3?knNjpIG>CdcxsMUJ4UO`jCC(^WZ0JR|^+vIehb)cRiP@6Um z%c~YB8&#MJ@(4kKtVEEt2PEO4od^mw#PFNw1E`y;>VyEDwZ~gQ{e?9M9iV?UGpiF? zKS?N{f=io^Y#`?`HKC;xi8T`3Nnqyc7xKCFlfjGdX|wB46mk{4xvE82@oogPd|9L5 zhWBY3DBv_m(Ht&&(`*6M^Zv@K!sbM-PYs&zq|HkIf#gt(nyBimiNdc=cg$@o=F)cp*T@u!h7wHLVp6Tp}hCte*$r1eOE8IW^;}z)lj5 zR1f%AKS_kvA-T|?06wobR!YUA@3)tcRJBN?@9QV=sU)w;bhQf6k4hhBfXl!YsE4GG z{9SQ4>nFX?l9C*u_+_X@8fRr#*7RyFxy-i}`|BqKvW~87JWtK)acNAP@glqcHW`6y zurhZTR!A6^&}485%$nFrr3BgsnKS|(^Qr7-fWH2wPB>6RR;@rJhs?G?)q;}7DK?Ho zN&yCaUseATqr~0i_37^F;P)Z@fpJ_2-U!hWV&f^|t*Tdf8|bU7`+Dsm%}ei!+WgIP zU!iGWaR}csMrEKXgE)gN&FfD$VkLRb5OtFmPKQHKtU7ggEAUb|UF`tG z&GcMp<2v;Whw5~+LUM19Yk0Omkstu%qvqCR6C6!D=o|(Qlv)`6lj>_K_!7)Tcmh|D zXVrE}88OoOs=SZL1-31o}a&O*JpOAf4#DF!o;usGIgy;`+)6T#Yf zP>;t2aS5;tJ)^Vf%vVD}LeKh1^`HVYS6INj0BG*RLMGGHe7JSU2@qVKy9u=4^8om9 zn=xO%O82P;F_j}Jxq9snVN*khVN6NPs4~^i!_r9MAUr}hT?$kz{XI_|=QpUeupo4b z(AudDmJ7I02@_x*C6Jdx;o6KHKn2pJP=Sxx%`Hl70U{OaHVKuHNFVUdt-!g^@q!=1#00NXR7>?M6BE#d+7v{C26INkM z4aRMz-|T2T`MnWVHX<5(thWBu8xoh{uFz-=%{UX5K^7njXk9V}AZzO&bm+tFbQ(~F z@cv}j0%90ODVeZoW=3MoN=6|gn`tDBY-wqfveOd+>liSQOjt21Dmv|0@nspl094dI zrL>tUT^t|cwgd5+!BktM@IvEz#hC~r@r50g%lba6yc?nrMXe#GB_65xgd=D^1dym& z0ICcbv!;eB)f`ZV@u5vNAVgzI0|OKhS^+t7_xhV;5!zIuMhC0Vg4;2I1MnJV^%%`f zTI<0%8EgF{=$0pQ(K{-@w3YD&tr}s(NqTQUe?Zby(Kj%xDuqTt{XEioL~Vo?PnyT3 z3TlXgDY+#z6<{#i#-EImSP8}-Z5wc$s6)AoB@^<7tJTCS{5q4(SOs4>K#XPu6N}_B z8}b=QdM1brmnsM#!Z>9^ZIEo`2&lSoCPB`Cm>yo3TYuAm40f>TOEtDz6&4_uDpVzt z0w;7>A3c)e-nm?nLI7=oI2DYwp@r4nf~iGn1yeRMHp=v~G>_0B>yQJksHVCg(Li>$9b?tE_UFCtiD#x~6Va z5cE`GK0q=cwC`L$i8jgiA5Vj_%Xul3Jp@~U;yL-4|E3XMKdHJyZo8d^nxhjk?EjRo zE`xBDbQoVJSKQ4VMeN~x;ptl9l7gLI6) z>;mziBi_J&!&_0di;ZUafh@wRiFp-*y@NfL{mm0+9gUY+f3xyQL8r%yg@l7h@*Aw& zWk>)WABMge@(KunO`u%lVl@C3z9NqJ9Y=2=yBFejEc>wf*yeQtvMKK(LODc@O`@hq z7HFC^fR<^H)~l7PU=Nh4SEN-@wy0_H5NSH6Se(Fc=I~PN^r;1DwM2(0` za#W%U-TRP{gazVKy?I7k({LnYh>#Q8Ccc^~vE(}wMDoszKfhO4X3=%MaG8+2A`+?t z3uKt)1noL{gvL1tgDC-(OzS7H@E|Qrr2>OnD>@IUDxQh;z!`Thopa(C9K>})5`(Rj3Dt{G)IZ*%wKFk}Ytu_0n3Be3B|BRT12(4G!I6PSP zuUPdgU)kvhdJYMq7zZO$l!(k$^sxAq2|AVL&qY~4fGQVRMXRBsN!3ZduXLWyfAUkd z6!f&Yi`?MKJUm;2(k&P9*;H@B9OAj^EVDo|D?1m%ePZ~q;Q%;W z!iH9AT!3NtVDAtdqGq8rSFn0qQ(ALY4LDg@0T+RzN7KjIJAIgCT!uUwNIOjN}x!WA;CqB|BnGcCa(Hv?-ytt-JG zK_($kG6W5r)tHChBmRe3rosE1N0Qw(tXt%e3sS^WKII*s*)GhRp;f8{{wTsB!T9i- z5a5Ov^P3SCJP_uZnhy>I_!o4P%TZ-dnoNGB=J#wUhz87uV8(w`kxHcUU@}kD)AT-7 zsURN=D%{eOvHO#X07IfWtuFE#1=}=?-i!b;0tpNYyBfk~8KCU;is;SVGEEDTwjsemYoX>;j_5z>yofp-3qu-a!%{Ppn}ErrEMmm^ zo2+vbKSUu0O`z+xhtl%}gBxu-^sjYn#QgE_R$yX`)D;xzb?AK{hMn(Bh>GA(prBC& zLJx(Ut)aH=TZ73f6qRIoA!4A3u=5kuT}(~vc0Fts$(}m#2cMsdlg=5Cf*^)Q(Z}IH z*84O=p<5rb9D!1ocQPiQOq6uo$r%mC6&kh8y-mJd(hKhHx5AHa#L}YcWaq^_x=K zdbO$w@3ls8r6dUD`k4k6vawBAnQ8FNqSA z(8K$LKTEXmL)^ST1d`VR>Gl=C9ef3m0ETFZ*SEroNuc6#qaPq$X+Dk%eQG6GNE48H zKKg=T^@+xKW7-A`gQu^hwfq=(*MB=ACeGAw;56N$QMZRGp%tCr!-x%-iCUb%M0gVO ziG-%fc*KZEKz$_b8@z^OA$SOv4awJ7070i%K#bSn38I0hh}mDSRzdBe=v6YU=@+uuG_7u61ZJfc zGsF@#vVKy3<6#Q#oqdoAJnC8m6PUDGIrZr6@bp6sR`W{ZrJtw5Q}R);YT4D1qIP4L z5J*{N((sW(qC0K-1d=L!m5H*%`cslvLbY8z`{4<7D1bE2PnitP4N_Z2Ch)saQ`1zX z{a23;(L_H2?okhz1w#(mlWHIeE?Wqftblao)Fs2St!Re~*dlC2)bl1AGA>dgGoryQ zYCS#o=?THB^A=F0Dt)9D6Mhje+krL8A}d;f4XRwB^p1WsFwQKBzGgB16-e@sQP;o6=s3mzZVP zsdjn2+63!16&x0VL8n0DkCuhODv069fLzENqt5k{#wwG@Y}Rl_CumUgJNi*or?^|| zVj1HUhfgNUL3N8Lh!Z6qFm)S2&6%nhI_Rn(F;uCYK8L>1hILQ}6dH-w6b41J@_Y(O5v6}9Vn-0<)$5-UkMh%~ore!iALCj=NqXi`etynZ&qUf+U zMF(+Wy;`8UqA@VStPn~F+HQMw5`uI{tSbg7%_iGOK8EpQm?}$EdH_JMB{cQX4yTaI z;H24#^*5zJh@SqDn+>7Awcsb|SGT37R@MqqDNUWLZx1yK?{%J`sbG`FuoC%9;`Odn zgwAmPYKTKLdPaz8uGDyjuv8+BM*%6kP=(Q70ET@V+H$>G!cDXqSxC}7saiSht}t|f zJXDAiQbaNdXZ@sPSwUfOsx>TGw1+pCb{<)&GQxo<8K!4zqCioITEp(xi$uYohcp*j zQXn8SnfOnovi@e?ek}oV%33V#iX&O!H8c~DW1SPRW3}@7NyJzhp-wx+V}Sfb%*f@m zX2#o7)^&>3kh5*zWXnl&g%f`X`w45dZ_w|ZSi#g9i9Ea&E{uXQX(IVCy(lVEt@;>x zYTUFlhyXz3)bz{CtQ_OdT=zf^DoolfeFNBAB(enWk!P z$fWR5UP-;4_p*LcNo!QXBO%$Ol2J5BdoqpDy6kV&4@yEt0_!KaFd^%VvIMF2a@8m8 z&E%w3UIIHOg-tWN&?hB@^n+QG9e^HLb<@h4h)j!VSF2s7$#{vNaTNQ@+w+1<(qLif zOq=JCPYtqS`6v0OevMqfJFt^eS4%3|2oPgpExjp?iWfCWYwL>?G79I!wSYHZi``yC zlQ>OPkXZr=hNvl6VxXy+Fe+jSEI~_PIL-hLO#pR=)!Esxd%|RQcJ$`mx0e@ZUteAx zzB_nxeSEb(bUk!$8X9ASNNsF_*;&}>Q876{KBw4c_%#$QQi})yEb?eghO5g(R~#uZ zjed(^vXF(&9+sv0-i7Of_06!Y;#rCU&!2N!Q$7JK9?E*PnIVBR;58}?1C${v@H-Gk zoU#xC--X`~MN6|S9WrLVL2C!>H_<^UrAL-h{dkVW>0sC2q_oxNdC;s^$%FX!;$+Lg zA_YQ?x}}ACH2h}#9*C0gO(?9n#AfC78o~>Q1oT5^Xl2&lY&=j1<{4;>=|l*k+&Yg! zqGVPwk)H+@CpA5tnUtpQ1(319GNu(p02c!t=8;TM2_>H1=XNYvLXHkfvZ$4zQj9{m z_e0SNLr{E71)+GIt|^3RQ{^IqP1n}jP-g)Vrt-sRi<&G#!Qx6A{5ks;#^i zIzmeIT(!48AuhUfJ>oZ^XgCIl7&~bX>l7#`It;cBVMsW1K|%}$18ts@J4zUHrZktq zz&cdzFafi{>u(CVMnE{Zh%Mf|O9a@PURJ1eR;M^;MXNE_Pa2i6d`y`(kE<-hx41YL zAe=vu(~WHdBPSM_OE@zO`w%ZxqrIiapamB|B}*ku(Lja1{w78b5@?jn2zZWha+bVK zTte~I3?{M)Ofmtxoe7YggqXlxM{v_|HAI|_%i#j*myWm{o%0*Gg%WysmV^q&;jl6u zm;wl>gw?>wI;^jnBmZ#=P`_UMr|^{8R!xD7ftux`r2->Q$h#6=q_2b%HfZqR3-2P{ z`bc8`($DK~nj}pCh1^r+M^wU^jLtbu?5m*y)XLW>vmT&@_U69?#KUt<`9TUcSsO%G z5?O!KVJr5BsFU5Wd}I|sbi>>g^tiU*f?Q43PolKFurw&1y|&y)G?N3&fG&~Dtt27S z&eOYkdg(#wrfYStqO&DuHU;Tryb(F39hqhs>D-AL5?E0_CE_zIK3(AF9P$*qVr3CXUE4mu?gJ9zfL{{Hok-#=Y%xIHM3!i&r|b!e{# zYjqQ+M9f!pcgGXxZ6jLjWDy8SJI#K;TU4FH$>^H2rRw*wX}7W<@|GWV>I8TNF-;*L}Q%*Gd~Af8%ol#48a@U_=@rUD^{-=uY6fkbfy(NiPa zj%iLIaJhkjEvQ<}{!fvX`X!OPd=lHiwMD&96{3tJ?Iig&NyoEh$kvrb>3n2%Lm#MR z0X7;}-P57Q4-inF5Kh`XiwZ8HjSnXAWmon&7fE@ zwC6MqWQfOPPaw(Avv`Fmy3-DMyrlsMnnXyLks37zw#GT_z>0w3YIQnVSA}3k@~{wq zDeBsYmPCx^{Iau>;@Q^~9}Cv`_NFAk`w1|;}!wI-I-Fv1m707-(NaSrk{z3^FWL$M zVgXzNeAF0F76js=IuH^)e}!EgrbWh6 zH7f5x)?GSWRgD?>MvE{#AP}#&DpcK-@cKTHXyiZPh1(y{e34v2pi({Q1 zMP@{g=^^7s^-@PX4+qF7<7Bl(qF@L|)7&swlOfF+!nhPBh1Y8udr~V+Ylqm4$2bPv-E;cH5D#%U{iHu6AjC4$13+bf~FBs zr6`1#LtMweiYP%G2&<{D61I#hw!q_EVJ}5n@>qfqVWn8cF0a zwvwrIl#hCY;NF|j%}<(4ke1bB=3KFcVkGj#N75UFuVkd)QPFcvAZdwKqylH?ip?vD zI(b)WkdkKVaU7FuRAN{O3~W49qsX1kgB$?H%6@)XSrRsBOW1?Q~san5p!{z@}XMT@;L(PfdvW^Tp_iiEaikm>#0-H^l~G z-^}||dMV6M;?jj?8H72k+oO{#a0Eis@BXV zax^2bhE0tSi4_S930N(yA@&((=xtJ(x;qiw;$^Pb9>9vKUKx?$?I${zF(%`ZK_QKs3 z8GuGPZr#Na7zz?VcE8B7dic#6hMa`S3pF<+q9*d?nAG_G(-o3UO!0+#8HB0so&+7 zTWTXfXxdGV_Z=d2F!GcIAs6~DCax+zG>M~+JtCz&f|UQb4p+o?BkONQB50kh*^H>Q zaf)yGOk7gd=m9D=3`P%o#Vnltt3>lkL@?xo)uu8Q4K;*W-Fapb-TIsO0Cu4O7d2Q~ z;U>3Hr0Of@gVDj6qNs8lelr|TIYOae1{1MphjoHyw!QviC@`*7{$76*Q`HeN5Nu%p zq+Ux!3MEstRV{^PRYuEF^?@EL6Gd<-14#9$>FnfEg;Nz61XTkzbO6GoO%GSeh6$?` zdrYOOQ~=@woy9o5SvL znSg3E4E!ky#uQ2v%J5dSW%ie5HCr~yeC2}G2#`ShXlmWD;UAA;eL_YY#Ym%6t}uAo zVpsX4W`9EaNwm|^SUBMIlTavhW)Qv|Ar@qz!m`L}u6t#i1Q?-M9b%u^M#?aHT`8;a zBZuKg!QO)iW4AY@AloBT7o=~!3JQjRHw_2fsRoKkxhhxPe9WxS&LKUK_3YS#gYW+S z+rjg{-5i(vw`V`EU#9V?j!@~S)zuRgm-45hJYbXlNmZ`vn6?G7)yvk8stJsWQ4Bb0 z7i#A{E8#)!Opic1POJOwhktzY^$!RC`1adhp8tCA>(}4>@O(J7S+gMlsJs;5dgTaX zMRYb+_H|FZ*%$Y<-h=o=OX6C^jrh98GlWe7PRcdG^K`7n<0exV!McHOHvn)^4|cI zx(sSHzu6QXGH9Z68n2B1NB)?ou1-~3h`~^0w{CF)!t|iSZ%XEf<@_6EAIxBf7l%!B zg0u(${%zQFYLet1s52F9FkORc3D%4J9xQQ^R?qs@e6LSP{X|IZW>g{!B69H7OsR+s zZ)cT*^+vQ#BgPO|LIA}+g*ImmQUF^w5=foi9C<+#D?bFH6tNzpasHi5jR2((B6lyw zX0@T>RDEy;TCcVv;cN?qn|4~|8TUj?g3;w|q}#_^S^ASY+onP-OM4t5xmQEItP7x| zR=?~?xr5i#psv5kd`tHSjObM8FC5aLq}4*mDtHn!jjWW2bSQ|lrOG`8n^@aKAhDiJ zs!%jUiy0(CnDM8)M96n5e5xe8NcfO&O|31sBslHtN!5#*+5|5Y zq-t4{L&{0=S}ar24Q*s4A&^ANsUpy~BQ^PlCAB(L~DRVT3jwknL=Umn;ad;G1&X6>Qc|;(P=!mAWUA zxu$l*9U6R9!@-6CKPm{iKA5 zxjK8qUlx0l!*x8aJZFOKuLB@&&wFV`c9@MxKu!d}KY&oubDQ#Uxkjn9Y+a@?F|?u6Kqa1k=I^^;BS zlg}h<#<0=8lU{kFnNFly(1j4kjULh4p;BITGkp*rJoR?036cXeOBtl+#tlvu01o1k zlPFY#DqR4E1y=_S;H;%r7JLQ4-^ zhhpdlm1iIi;A$=eXqRb^EW;Qb5@rZ({iJpd1ZCO^6A0m?llF#`$tb~0ybL8Ll|3Gs zdj$l@Ek`B-A`4Xi3g;tnu(y3!YVyP|mFsUxu?c@&CdkejPBx)9!YdNsc?O?)Ulc^c zeH!?Jxzg_eA9uy6GJ=CGbMPCdU>F z3^c#~W;2d>Cr~RT3Q)|>Pz@<&noZH`$b*^v4NnMwsLb>HtOGd()|huEG$S5Ki(Y5~ zs}EPJ>mUwHL!4Klkp*Q?f_Yh=CWW7rX$3mIA%9Ag%_8I|OqD@|DI=jQ2z)VS!5T!c zX1Rt>Cil)P1*IV<(~-gA(8igUsByG-hDDVFQ#GDX8Vp^n~TfqL`J{7I=qxDU+?~Qcz*nPZ(01ywLd!hWq1Ac_4VcH zi?`Q1y34EoUVqvsmNC1D?*z zzX>7$9V|#*-Y)vt?)TH9-P6PKuMO;6UH!D%U2cE=-R}Cw(>G^_Z{F;VH~rNQyWe-O z*ULo78R0_wF!4|@fcyE=RUUnzQI|5P^Nnlxnd=`Cv3`7Qe2CP z^mYWSsJ1ucV>9en|-klE_UC=fR@31gQS?q+}lara+BH^}n;2n8Am)(Ffy*#~gegAlS{pRiUFK;h_YeO(I>eV53W+!!>8f-+PH~d)p(5Q=g!X&1j zA3kaDAlZ>P1g>#DjpBp@AJu~1J)jXwL&*8r$)lIM>)rY9|KE2%eE-eUgC~D^@|VxH zt9`b+y1u-4_v`MD>$jJ?f4ulVyQAyvU##nKea6HefU#h5ph?ZPKBj~&x`I@2PRe_w z?~vbWt&M&t5wwL~ji8nk6n@PL!WgZZ`ZP;p2BdOn37c?8}tgIw-zYN1Xm`0d;>{N^pO@8yHX(vn;k>VDna z1D4w+e|Prc;`R1r-@ZLRvWB;9e@wji^y2*F^yU6{_A7sPbn_zChYSnR&xK4FgOEiO z5a=}>JK+bm2wD-N0s^{U$WNYa|FD0BPcPoQyNfCN9pqY`UAz^4zP-5o+r`!Xg@5;h zt-as%SC2%c%gc+)yX7*Gl7y!Xu}h!9cHkbVbL+f;fx@na4F;zkcyw~P+ueOr3vU^5 z!0;qqx7xM&g1s!Kg&_P*H2%+ zZJ+&e`rrF-x|fJPd3LAi+^g7oM$fR7UcdVG_2J8__4;H^ zWu;t^E`dCzg^v~n5RPe^{{E%(S~!tRPZu$7@G(MMT)Iq z_<$d6`XC>c?Uio!ML(ThAHCZCdH;%jJHNaME!*1r`^EL?$?1`^l`hWrpSqFA{<5vY z?eFaKNnd#rZg)q|fA{nrMA_}1*W1Trxg6!IJcplFQjqAXqV*}xHDlR8yUg4BE|%VB zyN`xv?G+bZlRq8p0GG7;GUSl%Rg9#|^aHw{>6mXs*Js*ZGDZ)Pc1$SO^XRf83;mbE zI*ZsLjOQOMNw$&vWz*&gg%+Gp5-}-~U``1C)&~GG(;FyoX`;jpkqHqkng}(98Bmj& zLwoeE+oRu}?hl9d{Hyhwa^{E3r+kDmA!Jk2Ldf8ka8m6PfCZs7K!<{pu!!B1!Qe}h zJr^bjfHs)q52iy2NYpWukkrLWTHFfhq^1=}p`6n0Wb0w{GnW$m(=?m~7pHVEp8>np zcTlBzN19jIyNU7UqZ^Q))LIs`hie)E!{*S*08*5-5N~J&jY5L02}DnAPi(&(x6s;x z{7B*Bx77BfMpdXR2ey^v0&F@zEVDZ%SE}qs=O@?yzKb`X{$;-`pIx5*zPl9S{<*un zYL_fQ$c+GEfe?_KtX+&5qL7hXG#cQ8wq$hsephZU`J*vgy^s(!ZbwGf$WOg#}6 zq=a@b8$t;C1{c`!uZPFM@MY~CAC0$$}q@G_mrR@9sn99JC^TCwSAvL4rs*zS>^#{*Kklm*&IP@6uFvZj&|ycUpZXMyhZ1 zzGZk)5!&(ky+?1)U!R`;>p#B=+Y)`C#YahtL2xygE_xjwsq`eg%Kt(f+%D`tlxO>> z^5*)L_i=o1`ELv1UkBHxXS?HnU-sYcu0pMIb$xkm1n%yS#x+8_!tdQvCSd7EKBJD5 zsf`Q*LKp_n7(-Vl;jOkOR{=6Q+5|ljq$L?6s4ZV<(;ddy08>Ndz*8!G2gD4uM@H|ttQY;g@x8j@9KH6TavvI=on945>Ld3Z<$bKU2{q4lZ+7R$yYr(P z!}~T$2l?z5+4f4#-n{op?zYmi-HW#`pIu!4bg3I$*CTW@Pf0RWG$`ni5()Ie@rihI zVj~FSV&}H;JJ>$4Ag)KBY}Aq4q@fmwzKJjjlfe(b7TCx zznn1OUfTBp)AzsrcyPSCE$4py@#*V}^WCpKto3@lL;58=y~Y|HvW-Ck7CVQj6A6;Q za$`8&ccM3K$J4{3SGxx-$Nqh62~3sz>*eX$`aA4*G`d>KPL|ER*3^Wq6su2RS4*AX z2}iPi5+<)uC-8=ba8Xguh)23tXhZ&K@WdyE1V-_^=|S^Ku^pzAnp1*3DtNN56);%a z5ym*gX8le767^n-&$9t@y_r;#U>I14O0>z8DgxX-cJ0o7y7U6o(+|qx(t(w5b3^7v-Ux)N0x)Yzpr}Vr2gi=r4mKJpVB+GmapY5o>0wJ4K zIHE{Z-Zy1ot?L3|z@y`$83%KwA|B?PHiOn2=s`K98072V*U~Ud=L;$>cuJ)lgN&O) zk|jxb8{VW}(P5KTOENG7LFZ4?K&1&xsktpWy3XY^*KbB4R~%+`fb2+(iTI-~GCHM^ zSJTqRB&*X@$PBPnKn8~l9pd41ge09!kR6$oPc+;J4L8*Z%j?`z=qI_YC^rydN@W6j zV6gpUi)C<$DaBsPS~wVwp#(GyypwshD5zx_?MWKmDH;#HRGDF$)gR~y_C5O6NZyvm zSG%)=f5T1gV)uk50P>MAgpy9CB@u=l^O1m7c4DY4hAT8GpvqrYkmDF&PJzA%rxqG* zTKYA<^kzeH6*(li#xQT`Vs~9w{N~-hF@AJg4;)>bA0NE9yui^NUOl+dzYIT!-I8gm zPGi6^uAVh)2021qz{nx9${cPUo&D?h^iRTxLKA_jOR@}Isp#3HiVhKThMyG#8qpt* ztjE;~Yo@RyW7=^#^2eP?ZQBB)#ZBNg_4d9*-Nzag=hMSC0e#<}pIkUp<{lFGbt{XW z|8ex{@ciZOCN%B;u7fakkFKQVv#XbPudvwI^W6WW# z8^a^7U=L>-PA1Q>P?TX#f}lR(gv9x5JKvJsr{+LR#8Qx54NVl=MX-LJb?n;>*y)Eh zpiyS8GEMjWquvrIHTQ0hk?462@Ue<1D0V4I!G{d(hb}z`K4`nFkTy z#o|K&^#TIX9xmDdc>ynF9ZY)v7;mAsj{s0SDfePc~j&YSmxXVQ5f#WLhvA8a~ny}!qR zyUQa%C5^5F86na_4#OR^(d#EY_S8LwZb;$j1^LPmcMJXy)$B|82l3FFyk=%o$C*^IY+#EuCw6Pc*hXW;jHA4x5WV>(^%KQGUSPo%6Jbo`WEnf`)i9(@i zpc168Fhh_lX$tWz;btt~8z3#CLPu9bn9pvw99g8TBIo7D8e zu5@Btw0RK|PmoZxApyDcN&TD%5`{TQi|KX9t%)q6yXWM&;%i50{uQLXIv z#T{ScJiOz#N4v|PcS@M^ZxO;nddtxbeoEu2hyon4s}QNxlDxWj_>kEqDxEy(0KLfn zJSG(Zkq_Lu@*LFk5JQA~qa94H@88l70L}N=vKl^EMizkbu;7)8jD=%(p}r)*c93`)2YEsY<$G$ z=EbbL^*5OX5rvSWXFzBGvV`iYh4vf75^9t7-k$Q8wYu$~@neve<_8)} zqoS)fW^)@^U)peQc1ICbcN=kP38Z#}a!Qv565P5H3ceeHkWENSRQrM3ULJh{UgVT@ zE33QbJhA`~1YbrcT&R9nz$DP#!o&~w(1)+mo86aT{o9X#-rFZ1GLc^&g@}I>l1MCZ zxq;$uE-%tfT-+%q-iwE~;J64ikW~#XVyZI8rmF+PBsjBzE3vA(s&&jl?H-*RonODc zdyYk}+vj|f!H)M7|J`5lsE;=A(|ZWk2Kc((o8K7sr~r}66Y;vC)kkszskHh=;K z@nt5g{RC24{VGy_X<-VNGAH%jP5Qa5<9|E1EO*Brf!{W@D6B&kQaCF8jNXxZ>$&m5 z6nkC?GZ?2HZBg<)nT(=*UnTs!`~Tmb0$CAVSKHtF`7H+fERN)()Kt?6P)98dV2-sY zjfn9x;Fg#|{|0*j?ii%mzHdYJ)Lpm5$*QMWH;hIF$Pbpz5L#9eH;8*XO~PbC2Xw|( z(fO{QghKEg%|%T~2!o(IZGH%mfGi+6V@e|`+%QPnrES>2&%2|G%j2@L&G!-7IV%86 zLWvF~q@?jG^MvGP)RAS%!89k+I4n23hLPy$TF94kvxVq#dqdOP$a z;17QZ70*xyTu#|yE;BJvu0GxhEp)vPDH-!-FxwWd&%*=?L*jADF*TA`VNFhs zT{e!=*gyn~gE*8=BkC|KRMYnbF_=H%XL9T^&XD%2fddzAVpypG$4XN}LDl#G^DdI} zyV$6aHrjeei;fU!xQq5lrW1IUS~Q&&tJm`Hd%jD zjltsxyMqa|?}5qsOhjR44dkuqumKx;@@aC4UYJF#O&1c-1r*ygjL5BA*q)5G`BhVLAm zOt&=3D>HSI?u@wJf&uD4?m9aMK?+i*K08r2UESA9W0#0`$NDx{V15Rs3sS%}WW*DL z;~A96xa6!4Ke;S6*`UX!?hB|9IOrthBZWMQyRnr?8U#h`*ie~Dhi)j_;EBO9t%i!- zEQLnP?4+)0d(!(H{0H&ZqPP9!^dop1-h2Khp!H_B;C30rtV(FCUI zv@TkM$%jeV&1SY)8cxqrkzl86>ZFhS(h(M;plxlkFwQ93wy1vI#R(pyE)8!*n>I!|{e zlH#c_$xwfY7DUDp)}b$N%#;Des8YhfOv58a{;dl2qRrHtg&tY;rzvc2t^m!i7u%B2 z8+c%s@7_;3`xbrs3rf!3*LYSv%vI%t?*dJlBINr4#890hB;n~$3~K7A;s=-T{QUS9 zAzJPT0V)b*G!9PZ_e_MKGM2Bnv`h5bLxU{zTHDtNE!vtV&-S~&d%Q~kKb)SO4tfD) zTBiWnG>~pqNt#FDPc_3B{Ulezbk#!W{)M+tpW9FS5cVfa_67g%JgA(unq?IkvdkLI zMV3GoL*wn2pd;&B%mOC?rrhy4QBpQ(!ifzJ1S6{<{Esf4)a6lCO8Ul|$nTXsW--!S z60s^qgl7LYQNp9kvky1PeL1!d%^!)NfsnSpnt>&Ws#9xibOeA;8P0@M!xeThjkg&0 zh~}nSBYH9Hj`EfFtw`gI*@8o|J8dYVp6U(C8ijU8%`p;R zKdDGFct|{Cx*ThXTQRr+qSJ#yq-0J{L~l3zrm;yTxUP%G1h9`sV2<&iaOxd8!~`)x zA&!whIXyppu$b+Q&`ovtzxIjk;e)+_&8{zB@9wpDC71rvK3xC5>2y)ioPEMTsWi1r zx;f*ygv9ayn-?M%?GNJR{)=7LL)p#xtY}_3ddHKb0P#KyyI6hZW5}~9446JrI4V!q zPpZphu+4x&t)ri3)1Mb{j9P9thTrsMlk1**qEv&dHq(g<#aNqO1anfE>T#_zB8q3rbh<@%s3J9L08th(;lbD~=BPve(sqnbpZmTEtH zkQTkQeNZddxEE4R>+0y$zYb0gPw%myVF6Q+ZriC*#vG`WJ%P^L^tR8>WsFS(e!uMR@LX8`L;ZatDsMl) ziteNJEkLtvmiOfDXXZ5SFs!jA6JW`eo8f5~7na5y+uIzw*m}iVH3`i#3kL?lXtgN5 zLCq!`jICnqv{u-DXVI!Nzpdr{jP+{*Q} zjNj*zC2pbFL5K!aEk?zo1)8!%4XA#_P+|s78Y#G8g}14b_b2_vuGmOENrHR|vV(|_wH^|i2#&Cd`e1LgJDFic(ou|=K+It!BU8+F9SjCSiEqr|uu zIe4;(T0J0-8B#RaiKLUvAyWm3fOh+4qUNpx8s6`Hv$$Tf_6oIRM$9J01X+SFfUiI$ zafRduA!!PT;m7((#@yN^L&JH0s@vg9c_(0|a>{`(>6fxtT0cperZ=!cffy1?>PU)H z>q#CBouhm+5i$u6{8j#1<~_tW35u3L?p2s6V0n`ndy(16YL4$9r86_9e^8=cH4ykG z0hJXbW(r5A$8r;LYRUlW0)j5N+bJiXXrXkh#-~V}+=QV#1r@V|4rO8m zG>tPmW=WT;5qG+&cN;UJk+N2h*WqPG&7`3h!!F~GLkLM@jbPY;hA|2)PjP@8>4oYs zEDKt(ZTt<3en2nWM}zOr<=KYKu6Orp{$-aT--JftO$n*1WtT|KSg{a?VSNC1! zzw3VX8GvT`;_~$R-OmZX)&+?2$FO^9jyUn(JUY!avLBS#01nCk1N8m)6t=&&w|cA8zkGLj{Gm?&qET_D5H@^-qSix5)g(jx z#9d0g>HZH6`cp4&4_L1c7at3hM(0CO^b_`{M!h4%SrbYq*r04_!#7QOy!n2o&|Kaysg*JU129MD=r^!QaBvl`GYq!<1SN`ra}Qp03E$=8YpoZU*!+8GM0#;wSYAz z9&KMf?U>;ebWL(zyeua!{3J9kQK0`t;A5D)v(BD8tf;ne>)ZYFuid+YqisCwE(qN$ zzq=Uy%&nWRb3H3lar=jB&~h7m|KOJ7U{(dNicBImH{)`h3#kbLC*IK1Tc!8uP}f3s z?F%zO)RIaYba0M?fG_I24npNP7lCvaV~}>gUOX5$ess1WZ+G{08)CNQ_x?h(wYERq zYzjVX0+;*SPU|AlXY7}k959A6hyw$yep!Wtp0R_dxWBM{P!{)$!FXaHDUN+mMWdwC zD_Vl;1LO*Hv>~m|JwonFbuWhDlZ>ZX)b@}7nu+~2VQIq@pH?%tBB>_(k#fOZI)6Bn zb8>Zbc>ca7ZMkJ3L2iWcu`)}V98t)o5px8=qp=p-$j>K}6R)0S7t=^+VP~HtR#j&? zp$P}v(XHNWr+lu3b92{ekZ`1p2e~x6H=s1`(!+QfEl2P-{HL`|b z$#R?AHA0YwlY?5;-z3-qhj8@j&{hQ-A&;)at?Ol7a0kv9NBR6_>ArtZ-S%qtID^D< z^zZvrG@=6D7p0O##OgHzqmG~zs`dE=n?`oYtdKId0ig{D+rP`8j}*!kAMdU2<;5jm zG0RQ|W>P-1B`0hl7R8-kRZ5)9*v>tx8?FY6ke) zpx*k8#C-TT{iR>+*wC!El`|MnH;bT9?mtoN>?@{i)0dY7fW7AGHa6areQV9PJYhu= z$QMKpH>d0d_wf=?Lz-@BYR}Lg8Jj_khwt%56}nzMtX%!ch}JRKx>CkVC`_mqS6eFt z*^s>~Dc$yEH=*So+g-m8n+U*UIR|`)uQkqJ&Qj0+4oXuE<2Nme+_Ho{#?CfCrra|nT_Lon96OVf^H^>moqNK57~ zGd~dB3c?(>g(d2hVa2jF{u05gbB0CRp8N$W-BNiO2q=7$>T4{9-=s`oQDb2P!ITwb ziVb-~9Jcd)r4no3K~wjS;pX;lA@YySYojoYcL0}>F}Pk3v;Nj>hPKaR=1v^+A7NG!) z1oZB3ubUR=Lp=EYA%EzV^v~1d-NlVk@~+j`gA>5y4_mh@xyT%oDx7HiA9Gr;>E8vf=TVn{*6@9#I12V%WVdV^( zP(%h5GpaMDN|(|wEF=s7uee35+`yq9s*CU2*a}EHa$g{R)LsQP1UCdwbNe9WpyOaY z`2{Ckz-LB{dIyEl)R34Uui68tP;x2rMV2#zc%m#wUe^JJS-A}bxC$`h7 zgmcNZO%^#U0Nd&aI5bdhaQO*N=P=N@84{G7V-byRlc&3VdIGDvrp(|_M@H_cv;D*W zmgUf^?|%C288zq8_0P|K{QAG(C{p5;dF$hYcc;78e^P;teRn`-3U zhXuAdH7G=fo!)RG=>*Eiyo@qcy~&ACY3HmKqb#(qhLyyP|6AJK{79CiX8{iw8;tpu zEg69qWx)a=xm4dZB{tJt)oquk@ifytV~b6aof%n`o~+DtW>&e21!^QO@B+y@HY{Mp z0xxD^dBuVS3nYZFU;%6pe*kL>3B>Sw&bjB_h&U&zTB7XfsLYBRaX-#^Kc4q_pSSF? z7pGrd9bUb&Y9`8Hf6Ha5rUHruGm@kTYbg)wDT8x5N2O7Ueso<2XM)-ykkfOBUm3Ym zo8NQkc;UHFduW=WDNTg#_l^U_R;!#g0)$PcfZT$_AW}AY?lwZk8ok+VT1fGy1(|TMb{T ztL0Mp8r5EEjA<-r)(wiZ3UrTfuHsL*5i?S>M3a)GqaA&NZf5|r1F}nwQD0={<}2cL za9UxdAiGhHFaeED$`m>R2S7)e*WIovY_zI}`cG%leO zAok=>;AQ89{Gq%X zGPer0#gGKS>7@bJ9vs}9T_3z$-y9qsA1{5jgP1`ItV7M2xM{YBIuHUdGN#N&y(|+# zXy#~W?oY;)I8^2^3YCsc8&t9a!7DD&->b{60;;VQU8e*(=-qn!^*aio)yAXevQf&6HQ7NArH*^hLrgd*J0&p7 z9*_Z&3UCKv1HahdTy}c%z%>JVfa?L~^~l6p8ee5QWRWd1bU2*Bz&^y02F1{{u}OT* z4$@zPSF3J}L74Tb#3dvS?b#Zg!ndRZs`baG>B>7itFomP7w%6Ah6jbl&O}|HG@m*n zhLHjk@Q79=hOaUVX@(WW4bj}Z)`zK!7JfA!Qx}z(7&)Tu>dz?B88*qVO05-w&F<|_ zgF=DcA>C5iWisMHeV_FQ=X->b6OR z8wF3?4u^~%gimBUg=&$ZIDz@`EmY8Tv9LS%K`~pL%IWK^%Re{>nY~JL_2Y8Dg zZr&1t*CIn-d?@b-{c$#a{d<-I1*^9&t&Wy31!RC`<|eGw(D8tQ{VE_JK(v)i8Bwe6 zFAz7y*b@5!PlXH%_ORUMPz}2zg$YO#OMYH+Q0bF5I1h*DMX) zTX0xsox+V1f}ve$h{1ZaIo=8Z@MvydokT2*uxXt3rzGnR z<(thluyAkpaNvlDBQV2`K%|+dXJ%QDUOWEljCjimOK_GxbKZ0Qh2j7`1Lo0$)QF9r;WLw$W~e23d|1+)OB^e0rcA{_DxmKmn8kWd%s;RO`fj13Mw^lbOV6-8DDrOxvRl_7 z!U-+_W&_HEk^zntN;S4|Iv3EXwIic(Qu{?hlWba0660+etxKvvkr`;}$yfsdiOHmfWUwbgBE58D^caLfCaA zaQsp|hKf?jqu;w_R@d(oi#Gh@Fldr?h0vw`f)`*ug{iY~(cu!w88EeE^I4DmTGJ?& z+X>~02eRkfX<0KZN|q1wBOFcbyt=sAe{4SYx79$tsF=eK&ZA?{e~-{pzqw{D);Kea z^hz24FD#6LT1M6iL3M@bZoXN6=xT{#I$Z!V+Btf5C`C9osey-o=5~kkr*77t1=Pf| ze88Znmom!q5v!_&jvqHn${wBE5xLPlH3i~m(4^<5pHz%A% zK?E@XEui`Lq^?+^7!%cB)IkYd?6ho*WLH~3$P`G4s$XB6Ps+A~xr$P6+TF>SQTO!* z2|_{&Q?8P!sB^oA6eAuDFbO-K7L-97YP!?}+zZymcpLdyx^^hRLXd&-{TgE-GXMM# zVw|O>FhMs*BmoQ{XAmV5;H2ogTwT%8>e{keT)EiPy7PW;$*rxY&DUxaQP1qpLev+~ z8IeLWr_f%EM0=8@d`KCMtR=z%gKp|!oMHX+n%;%!))-=gkL8TG0*qH&L|wjG4na$i zUlff?V{J{DlpO2&=IZ8RiR!aP@=bMRx-M;b)6cM`dcEW67cx4IP!&Tt#AFe(uu&0# zZYiumDJ6RXB(%Wxal4U$%yrM?7d}cA1gHf|6p5QIv(_S; zke^0fe6#ck0sFi z(Z{qZ=w7Sp!X}tSgLjsa-GY3f$u}553dKym`@FmOCb$KnN$P_RamMl zV8g)llS9y^`S#$^z?qPxDf&i(SP}_a5vrDwm;J3K1SySV<$T8%VUYeg`wELYbVfOk zo}sWFgkWYVYm1OGz$;+bir?A$^*Qe{Z#O`+nRL6yF_QAsmE-PF)H2aShNEpE5+9hF zXcq83GGZQS`L+*O&|wo}CcX)yxho2j@+cz5@+o;0q6*X;mL?}6;@RJbxK$Pyc8C(F zlSFc3t)=d+iZ5pp$iv>KJTB&UGYzj7eDM5xT-)*K`N5m(Nl&9*#ov9iCO%g0Awzrd zu6LcCp;N@8Ko6BSJB@rGHHc8ytrH!5sB4T6N1=nOzZP|Nl0x_tdg#&w1Afc!8{0Xm zJK8TKBbmY6ADOf#l0B?kru`O`U~bc7Gf)g86S+k+fI*;!_QY!mU`4bMk;FAGI&n@} zhZNqORx_YjHOtH+C(@G3>Rh_(7ZHZnmdTr%cpu}801OPCMXrhlDa6HM8e)7jo21Se z)NOlsZn9tZ&OW0OqSb(Y6!e`QY=8XO-+|eGw274sBQM+!agEi61h05Oen8AhNRP&Euw&d(c}FVN6YHA4u*U8i$oD8_E&LwEA40!-NqbgfJf-eIGr6O_whQ?RjcX2;d>K%FbgR05PG=n-EjC^Lta>66GwrJ^}i~ z$FG}yOQ2~ZY}#1i{rQCjhJmG(SYT<&F2E3Co~9qYkpOH?8)5`Li3dI34&9eqaPL{u zUg>ua)`YHTJabq~VIyVmc+~8;R9iqbD*YJ)8blGOd#vISF>7rsbWmxc%C!*hEPrE( zfho|9{-RRUPscY>d(?d-?f_BS9@83cn)*FB3h`_3_zjs-$kG}Xc~GAL0n0e<=su=o zi6?E#au9>>6r8aM!XHH=g)#hK0@|9L0E9}W?B4_RpOJW{vUAa5h~P(%>40$Ll$4}& z3r9p5ZuiLf_TOn=&|1OY0QV(^qC}m8)u0G#YI#P%kj6|*glg+H+{WIVpf0EB={723 z8SOSW1yoIlrL12g0_6`&Jvg}%10zqAQ_Venpo5bS^PO~&eq$9ZaX7|3)6QL{5|haf zAmOy1l{4PScQhHR~2zreiRx5#Wm)A}nG zh$@D`$2?%9HR~&)HH>@;+F4;~B@@QXg^W_H-6k51O0@)U=#g%go+#o$^ z^JBX2!Tv!V6!R3lN#;|EOn#7tCS(*#*&c;Zj@tK)WWfb-B9c$Qgb+{ng?gQ>0r^p| zQyh;7WI4lfF3R7ZyuQ47H?DtsI=~~oN5@5hadoHr3b;=~qO_lgg-yfk@0BVG%mfeH z8kg;=`(+#xO<&FkI*FwTyatCaXJWqwBK!arBdP&o$s|&Z(l{#OfxkqNp5@U~>n<@} zqxrbux&S2ps@?_ zL!1u$+c^=RP@j^z^fxB0Dei<*4U8rk0*yuN2(u388ESEqo^dp@h{_{s;m#sfw^YiO zEVm*0l?`Zm%;wydp-CWEM1L!FTVLyhfmjiWWrdM4mDMDCsAwa^7%-x6X6<9=aid}G zfi&b3gg?Z&>0@S3rU8~^*rW3-uQikD?+Tz-h>P87em!$&4TtZ_3Rq#;N9d{1gah;k zxp|~sm*MewGS3ky_g%~#Z0JbeV+Xn?8hw%m%LlNM(|H8pVlTPK{uDSdY)eX|kF~E! zEz^7uJ!!;LtUmZJ)FBFg{gDEzg%A@6MWqh6>@u?-h71yBVx-$vFjV+ z4lnMNzK+$&Nw|SC`Y>FXP)DNf5%4v<80iDZ%4VZ`--W^ojDaXT*hp&&VyfFkZ-yu$ z>M0Smkct8w=%=BZb7P6{2Dcsp>G=!Sts#nXx(7+yiiht>n{_h69EiCx7tK8El6F6S zH%V01XrgV$<8_*A=F1(GFY&hEXxJ!_YQ-`YJ>k2s0uap9lM#WYC^8HlI$CBl*YQ^s z8F?>Z93ls}QV1pPwqP_q=uqjG3-21s!8jv8kj8fkdntBIT>j=LSm=)$ z^S#`L8e-kAouu`;J7<=bRab=xVPN%p~BjD zq<^wcfIbBP^jk=heTiCKPUS^){-2y@Y9;8mh5*<7m0J+0&Ivcr{-roJ$6ZJ!*$75U z#z~zIX@qP=vA zcJbxmU<9(!Beax7&bnx@hb;zGNVuX9J~4;l5`;3?)4`K-2~UE?u7d&2qnmfrzr?vBT*#pfyZ^w>8|XSG`ph7 zg%qq9fkv?;LL(^PyVTcpyk^8G%eI~B0I8-luoh@{=)c9~j_SDGw_Ek%x^(C0#zUmU zPIMvI3D{wJK{z{sD{5DN-Y!@FrD;kceVKp)4^El9Q7w8Jh)ZfGI@Bf3S(Q*5qDj0;bdws~cQgYmnR{`@(hg|Y4q3I7=sdmhcilDFKnVMfs_JYKpZfU+of43<0XRPvRm zV7#?(fualYgFsMq>xr5ax1Ond1~k3Y0r7H2pQ}3$eT2kjfNZg|8Rm?3CqXaSLVEoC zvsCKdAT~A5DpLkgMp(*51VP*byR2 z87ffNf}i@-`Rl1gUC#h<{{G`Mu02-Eqd}2654j!WQkTWL>aNpqL5{rK=w-JqYuKkx zT!grZ8anPuTzB_Oa1jYd9xK9yye*1^?VWZ#4oK3XxRfxad0yM9sabZPWh+D8rBE;l z=ZVhEk`dIvH8@Ih(Gx)hZuvq?nHag-P}ab|C=kO~Fqeo-7&U3-i)>7P$|M?($$$0T zBPjOwGitDpKs8_lqJD%aP|m3Vi!6d;rb&kQmikA3B(j~2eV_eSTvMF!6pfS=li`7- z0Zi)3)ZINENW*TViPI6l8ZpoP*@EPd zNx2gQdRi-Br$j&m&{ZnrsVTIj#bjXlzV{k@ue*K)@IkDV zUiM?@4fBram&8qeZ9!UpDN-6X!E~fp-fbk9ECQjV-Ok52Q;)?h5Nk6FqBpxzD57Uz zQ2V(r6w9b4^f-KgheC15xpIc}oN<}CO4!t*+FR`0+?KP4R>`Hh-_bwL>UJ|cb}C!S z&`wi-uO*JJIvSvOtf(WTz`Z4mLOPbKRt;ur5bl9f@;*Z!2>EQ7-C7kazbN<`bI-=B z&u$I%=x$jGt(wo|Py~n2zVOj0i>Cl(4n*gW&!M*UMWGqf6-pLM&#M zyFD4KCg~xaUTLJx0?&*zf*PI{c{waW%1n$0cQd#BccuWP>l=EVT_PE!8EzJg0H< zjUsvB!NHf;*ZUf(5pU!lqBUZLUQ9aJ$#RgGnFK8v=RDHUBCkDk3@5h3X@VLd>k5t zniX{&E!X;uorlh^D3@#%RfekMjT%!P>%ybExLmz{bG|F4tGE5_?%>z%3E97p^{i;E zS>#Tmj*o>Et_J9U?{Ysg>$QAi*91P%*ebA?^)%1b`sgh^<4i(-_KsBF>CxwQBecCt z+3IhQ60c|oLkRH=(kTB@qXK*hfHO9YeFFT=LV zxPcF5CHzDZGH@wgllTW21*)3%7?`pWC^H6xx>K1rRjp$4#&ruh@Pf6Qfezf48SH{D zmIY)i9HpEri;t38MQZ925(8AqY4>3n#O^k$>WP*VtH1)*ALS9kwt8&&wqR@{f~l%l zy1W2!2m~a>r1i3KP2|uT$|_s~`nZoUk>u6uiw~$ws4k*06S&98;TzKTnvgVOih^rH zJcWp&r}EHU&DIldo1j#POAIlw>q`7nW3uVj0O3h*b5EqtHl~v|J-FBudMxzs2qYmQ zQH_8ypu|ay>dl?#ppmC0r=j`yUxr+|~g?tGD zJ`NCo3Cir&iw7+zZ~4=k{qp;vA6dwNm47pkK18XLmCfT|;-QET9@M-D}*j z_i_29eEvqe%>Sflnfd`1<1Qq9l?Vp8Zo+1D?vZEMosf5C(}iN?AHu zF|=^HJsKoyii|R$G(OX$7~yN^Yc5W@uB}VcVDvQ*fW)v+eiT|4n8pdsPC1OY6Xv+oF6E{kJD_oEL&D3=w$bzJMgAm-UHP0PhWHl=V!9xVkQb;kdd`x8sqDM7k zkn$!b#mv#JgIdl)1GCXMZD50ljRytgF#o`vav9;;q%B~%#n!8h}I3rj=8EF|NrSve>MD<{b6am7DNK`#J(#4;5(ihWW z#rAEccK}f;BP3-~+`%6^(lw#tuM?C>fX@gF< xV7A9+ueULvAPkm5B!5kk(#Sp^v_>O71)Jclyq4DkMEJN4;9OhD4YVEB`EZW&;no}cZ;Tq zdmIcfJ=R>WN;x;hsSBRN6q4$sf)h@Wo8Qn}&sk1veg7ElF2)wI>eK!YtzqVIz(l})FAZK{RylB z;z3Q(bpm97V^s+XYkZLP(Xmp5unEnx2HUDsanmVaDXeLwlkvhAjz&uH0H0%Hl2oT) zoNGnqZ?i{P#7Z$#OGO+Kr(^lz63^V9rbBfrM~=$29s>!01&W|#WS|WEy83R|p*j~m zA^~Q1WGK?;u-t{3Qy3VofR$EL0^z{;L4=@vXVD!AUWX@xmR$oVA3lp^ZC7R?XusCK zZ2Jh5`Xf~kB0>e1CIN%Sz&b|apO7ab3iB^0o)+3dVrM_WZLDi&Vlou;bw>|yoXF(T^>pe z^c2!T@1R(sUIawwgRw3(4v-Gi&)bvY$c~V)VoT$KMYpI{bSKl;4%dXaa|8;ob30~x z`twPCUNqwwktxFOHUp4T-?3Bnp_Z0M0nz38mE}Uswn(^svDwCEu35<(^iqH5rdr%* z7E9RW`gR5t%^bIWB?#zPM|A-PS#GaAUXF<|NwOTs9<+!@d5jzV`ts`HhA7}xTs?<* z6Z`iIV;ff2uU;SRjPK6-(45@*vOf3GoRCELe|H5cmbzs4VvIMIj_E=?>Mtrdk95GI}bQDSJUY*B_Y{sQ|)4yC_HjDm9Q}0jb4gRu;V? zueL{jr2P?<2&zpmQkGDeMXQ<#&zN{R9B@cIP+>uTB<7F+R%Wp#zgi05z{!YTXjMT4 zrVXiP;!4UIdQszn7laN{toT$YD2KPt8 zg4s|5mt-MO#@=abC_0QH4)%T9~g=wLRd;} zCe-PVFhz#dR+Ul&BLSQXn@G*q`zOPq6f5SCg;X_GV1h)#0;3C`!Ohff`NI(M2hATk z@Yd^p_nla&e>TEz>4&j05N#9%JfnQ8Ui@9+K(a4aTL zlGa%)xe-vI0Ng>Wpa+j^qFY4PM17&NzMweCrhF2x884;_Q<4UdQXEiYVM~Ocs{AV4?Hs{dZEVu?|(`+&C)mSU7laLZwV^*Xao_N7Ykd zFYBUGFZj-RRepMLHTlu9ZVFswrA`V1@v;|hAj-?zx{J>0)7nWMUoFKdEHn6YriE0h zLX;WUnRAK8MT`Y7wPoeIPs2L50M(T+S|8m_#Zj`M(MWwr-A^G@-9W=$-M{lDYh8f- z?TNch5NWZ|$0bu0Bc3WK&^hfYI;_Rag_%1_m$eI#0c>P#sJql$YVsc(ZI0FU^|%rXK|4epkX^b4OU`2X$};MqrmK# zu7?nThACb~(NI$q@rJl*WSq8`@K_=>BD9NbnVn!*eT5K4K!eLj)7whLVH8wYSBTZf`^jvgN?>T;R zw7z%2f^g!!lqH;nvsElLq!;1aOr8zh%_?jJ>w?=dKgrIBrZgSw4&J|`X=V;mV0KD zaGCga&~rxGHiqm%$jDy-nuJUYy{)-izl)zJi&p8x<2pXOez%A;PK9*g+4l&xdH=rX zj9EwC#o`icD+nPNW$^9^vyTPrSGFLm$SBtCv{@HWp=~75#Dep7`x%o?d0p6sPArSO zFT?4NjMy;LO}dR8<>}z{M3^2B18ukq$yODkf-tyM5&gmE$V33myRk;;HdES!G^6t& z3`u5{_3Et>n0|G6adq=WDs%m5h?_u94_GG~I-F;nOYEG!choV^kc5d9KiMp=-+Q)A z^VqYib&pB4p{m-qSi5w9Bgi28pq3efl3ofi`3NuuzL-D_I5Y_qO3du>d|~DSMZqkV zhKN*vRW5M5$KF!-{POVXdTr;lp%>SYvw5?vXCso|=}+~Wm+S2upnKX*THOl-14j=< zgx{A=>l6k#gpR+mS@f7zU|E72Q95#U@lK%5x1Pt^vV zpKGBwA3Qna0hCEQMX@MVG}wdjglJPDeLe~_C{{<`Nl^0D;elGhR~t(EyHNIKOfqBy zP)#$`0`MqQa^_mo&5=+zO_M4z95^$wi6dIf6Z#~Y33Xd~uz`UBI3#RE`q2%lsD2NZ z00`XOHFOJJ!5&KOF>SCsj!-RCzwj==si{ItQUE2uYN~)Dhsh*#A1V`1i4NdvpAgiE zZPIXni7Io)u_EXquqFu+^Za5-7oHJ&?N8AX$j}J6v`fRiiN+KeOo@Z~3x%H0w85?e z2a{?ifCy}c#V<&qB&3BrC_e&@rgOvww9-j$rG^^9rj5Bsd-ffy=_teL;G|+`XC&re z%vBCKEo@y399?W9`nA?~{9n^3EGES-jeaQfX=lLJl@3~E>b+_<<<4xtG3Dnu7^?AH zJ-L?FSEAElMj#&VjI11i+)+)nFCM{4NSz>{inW!NtP=f`WWD)P!SI8DRb^_~?(~L7 zPVT;t-IWFhl&)zjkTb9w7&M6nPtDNXiU#w*pT4@#ur$S_-P%F7(N6%B;Z3D2QHb&s zF_?WvroIGhpc>ZnIa(s{!^5k7l$^eehdP#`l=w9}BZEzub^;Lwx9YpVq8of?3jNA2 z0C|E-(P~vu6W)`nL>|E!6)ACYqaiwIy-1E3jinn@=4)S*5wYQ%iorYvFXJmr{mU15oM7krv zW^sD#ioiMj6zLh%GzeJ~+V*Q)!cCq_+qo(c)?Y?*D9V8WqeB8va42MO#JbbbLn)en z6M)(pl3*k+22uSAH3{qyRLQiZVtYO_8}p-^_mIH#5c#@rMe%ZI(OE4zj6|4V;tWfX zwM3nKK$Zk0k%NfWi#_52K@4g6fLM$8xDCEWYET>BqgJ@lksq9C-q0#)>b$~1QG*lhoi6`u2WN4W<{Y$s3Ri=_D8A! zWF(|6ZHmHp9fF(a1IdXZA*vx-(5Wu!)uLu4Y19ppNj))gQU*k0+L(Umv| z!vylQj}QRG-YSa`6UefNN_&I?Q6#uVL%o-C9dbVE33uF@eGXaXhy`>6Z5GyhM$jsQ z)l`Y}cyMN_O`05(Pdn47Wz0}MFhSH{)o2*P!i4pVH4sF8D?96Gux!PK`iXJI?H8e? z`;;LyrWzBQ6AZ{%P{I-Y`tQ^olGz_B3lTX}F9PK1Vzu1$b+WGN&6-0`B;(a0bslsg zZf$dcPJTPncDnbL<-iP;=KcO$PJqpb68=vkW}5m~gfgN4sWLGjd@4o9!JQLs3wIE> z(95HQ11nKD4up(uEbGt6=QOeij=%Ws(}Pcb?b*RVg49Yj3-m^8jhMOzrRXmNW<6D<+g{Igj0hlgjU z$JJ;L?6abMX%Nq(0W2*vdVFRR0n+&rkpRF>$n2Zk_zeryuR5rg1Sh3t!zz-|>m`aD zoZA4FL5KH6L#7Y3J=~&5@(>AD!gLeT8y<_I-;rkKqe6^NA?08Ta~|G9gXY|8o;Dat zlyjBTltl6Lc#W{fH3#aN>eo%0he4=;cm%;xNJdg~s$AZl(8d7#lE#gsV-Yvgx$9&3 z2)9jVCKrSptHneABDRh&-45x$6ILQ;*Gq`aNCw6;$$$mU4ki+uFT={jD-(dR!&+!X z`X8RIe|s$N!ri912V|x1L4j3IkyU8Ik_zR0iuKrd`k=LuW7z0YmB2w%K0*icWCy-PNT3%^VM+v(*xe++2e|##N~WA(Y)M_& zoB>KE-?SAF*W7eSsg??@EYgVRPDLvS2SP9F=sN`-5LZheO%HnUbitSD<{1PA9l^^d zY^1={FHarKs6L3hm_p*H^a4vUC72v0hiG9ccMFBe`k8|H_0cy;rzzv}DZJRdFH=n+ zR3S&VJgpjP9~p}nz14xIQNK~@u-VuO3tHDT$85x&##;p)HzRGa+Sry;T3R=sUQiyE zPM&`GCPUo1bq!d{7)`5EHHma}WqBzHY9`ivM?OY_f_u;(sa!{vKn9UUSTQT~OPvSS zp`FI#4Gy1%)qw?|zX}5KWhRuPpNJ)ZYeKM)foRUA5t{h){Xsy*Fr~l)kQGoGnxH>1 zGxaNl9 z!cNC#0H29WLMDPBQ4kCZi?v{tk7qK|qL#iz@Ew_S%nM+&`XREs2SHXdy9j8i^Z-VU zrVs^u(0F$}95#|K-o0LOY-qj8K1X~6DCUj;jo>8#?O>mJRzm$E>p@$BfHu8gLL1x^ zTjNuKNDAen*gqkV6de37S3?E_WGf0`LF)Mcaz0h;@=62~7uv!98togI!q%m_P*5tJl+jH;We3ZJb{3NaQOuG1*mI*;ng?>SJVx8z!r$#;!i zKgHuX(j|}m;#4&zFdlO#n-juDBSxb=eLgFgg~XKLGWze7>P62nb)qy&`g%+##!Boc0;5r2(h^ z;tX51%mItcBS=_<%?9=CxOF7$y_zZ|$- z&I=`!(_TRGjdw(2*5$Qjem{Qq+0*AAzqvM#soNr^pUF|fyf3&&O z)E&F7KN7Zpm#5i=j~88Jl8i~H9D8c_(cl%B$a@D1$M25}$fbX;H=ZSwxui!2@`((` z&Uz3Wl1YsH*=jY>*CE>W93mWv{w%>xI|bl}mdIU!9ATUONIJC&wi!RJ8{eZxKQ>U! z3beoxe^jcHGK@4l2^4CVLKxKgOFGTpamzvcG8Hyj>CBm2UMlDUHx{W z*N`nlHN;~DN-5%r?KFGQWy;VuSwSRD#Qhq#B<{y&ZwD3I**xzO4s%PYy9D`NZBqv? z&W{#=!4jF?8(M5d&&zM#KeVH~WztD4^`pXms0Fl>Q^OQb|H1Md!z&kTV4FZ2d47!~ z;(JCrZ5uds3>5U(PI1#$}n^b>u-a^D(221r8jU98k$rm!};hjL>Nm%_d3EAPNwU zP^Mve6x0=%NgbfcGFdl2VjF&#K71`;Nu%}q*z zW;ggw2#tToiIj!~^3fxrQ_NLj9cNgzJEad|5*aSfbYt^Cac$Yk%ztxzwqEyH$&0yH zsm3P#xoeFuvxCi0+ff`t@5D@&U1&sq;$}NReU|jF`y++aa)6BP5v}7miO-`nl!iAU zV!B`Gd6;)mHqz{W=Lpv*ll9fM_2`y-N`V3iBmTlM2hxJsky^kH;W7~3Rw7iI9;C~# zd`c_QZGni@jGyiVWtgfMCbNM!#iD)~)Hw``i8?cjnz1Gd7r;X}oT|Ldrda8N(65cA ztHXV^j7_$5mF^476|Y~n`s%ijW8k*-<=bpnct-Cw ze)Z*>)3f7WK0Tg(_0h-AfBEL{<*K~>)#3H4)$QYV=f0x8-dxpxncAPXemL-NSL?IG z95DUu^6X}HJ2=bgw!hwfdEqaZJ^m{`IrXNCh}88!HLG@^}(xSAK!lRDYQ50M;{zsT^+ijd}8~1J~ij!3(9#%;K5(=UANEQetLNG z@#*>J>zA+N%Ws!YI=XmWep>y1|M_b^;ZOMApPWAFvv1YEwAWvpe&1g5-pxOM-4B1- z|Jw1F)A8-~*5;f2{bzQ5e&pxP<9}~DzPe{(wiPff48=YQz;f7bun@vHU)?e#C#19OeJckTGUQh&WV^_s7o zkN>&pAEwve$$=YxcJKdh`L~~Jod56q)%xc4`km>3cK+>q+Uq~`_W!$a{J+bB@Ta~0 zxc)dlqjvt&uUG%XUvjDK^S+uEOnd!bc|iWO?{CMq>;I?zEysVHH~DjUviMu<{ zw%32@=gs3k``!A6_S(+3eM*@IR{8pFKnN6?1^+7xUA6Ng@&pH2g{8ztHZ&-Vsmj8TIyYBXj z|GabjKc0?nufMwUz;=)S?>oo;(ch`x-(G+JVf&`~y7O_X|G9Jg*Z-&<-(G+Gtz6ym zXZQR6*gxfW-)bNHdp}#>++KGd*v`-Y$GiVRj;}|pR_(ay^;Y}+^xD3Eesy}e$L;uA z?YQZ6=j-PE(~kdn$L9*$@!y$_Yp<)ny?p|=wtrmxl7qAN=Hvf;z5ee^uWezpRouR} zo!7r|?AG`Hhw1p~^=E#1`wQFg?e%Z%od4>7O!t3!{f+IzSGRV4zO!@uZ?xm4*Jh{M zVeNa{&mTEHziZe3^>p0j^!jfmd)VH8tNmqq{dD^NJ3GH`$N#;Z-+O!cv-#Tg-sW5X zX!`zt{<6OFm;B=|xHawk+jV8Nw{zb-{_0_fe{X+!``-56zr5%8|M;}-{O|1? Ju=C^U{{fpasTlwO literal 0 HcmV?d00001 diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CXX.bin b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeDetermineCompilerABI_CXX.bin new file mode 100755 index 0000000000000000000000000000000000000000..631c9ac47e35575c396fa010d9b7b9df90165656 GIT binary patch literal 15992 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 literal 0 HcmV?d00001 diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeSystem.cmake b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeSystem.cmake new file mode 100644 index 000000000..b1b087a1f --- /dev/null +++ b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CMakeSystem.cmake @@ -0,0 +1,15 @@ +set(CMAKE_HOST_SYSTEM "Linux-6.8.0-100-generic") +set(CMAKE_HOST_SYSTEM_NAME "Linux") +set(CMAKE_HOST_SYSTEM_VERSION "6.8.0-100-generic") +set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64") + + + +set(CMAKE_SYSTEM "Linux-6.8.0-100-generic") +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_VERSION "6.8.0-100-generic") +set(CMAKE_SYSTEM_PROCESSOR "x86_64") + +set(CMAKE_CROSSCOMPILING "FALSE") + +set(CMAKE_SYSTEM_LOADED 1) diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/CMakeCUDACompilerId.cu b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/CMakeCUDACompilerId.cu new file mode 100644 index 000000000..4aaeb3420 --- /dev/null +++ b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/CMakeCUDACompilerId.cu @@ -0,0 +1,935 @@ +#ifndef __CUDACC__ +# error "A C or C++ compiler has been selected for CUDA" +#endif + + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__NVCC__) +# define COMPILER_ID "NVIDIA" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# elif defined(__clang__) +# define SIMULATE_ID "Clang" +# elif defined(__GNUC__) +# define SIMULATE_ID "GNU" +# endif +# if defined(__CUDACC_VER_MAJOR__) +# define COMPILER_VERSION_MAJOR DEC(__CUDACC_VER_MAJOR__) +# define COMPILER_VERSION_MINOR DEC(__CUDACC_VER_MINOR__) +# define COMPILER_VERSION_PATCH DEC(__CUDACC_VER_BUILD__) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# elif defined(__clang__) +# define SIMULATE_VERSION_MAJOR DEC(__clang_major__) +# define SIMULATE_VERSION_MINOR DEC(__clang_minor__) +# elif defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif + +#elif defined(__clang__) +# define COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + + +/* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +# define COMPILER_ID "HP" + +#else /* unknown compiler */ +# define COMPILER_ID "" +#endif + +/* Detect host compiler used by NVCC. */ +#ifdef __NVCC__ + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__INTEL_COMPILER) || defined(__ICC) +# define HOST_COMPILER_ID "Intel" +# if defined(_MSC_VER) +# define HOST_SIMULATE_ID "MSVC" +# endif +# if defined(__GNUC__) +# define HOST_SIMULATE_ID "GNU" +# endif + /* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later, + except that a few beta releases use the old format with V=2021. */ +# if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || __INTEL_COMPILER == 202111 +# define HOST_COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) +# define HOST_COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) +# if defined(__INTEL_COMPILER_UPDATE) +# define HOST_COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +# else +# define HOST_COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +# endif +# else +# define HOST_COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER) +# define HOST_COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE) + /* The third version component from --version is an update index, + but no macro is provided for it. */ +# define HOST_COMPILER_VERSION_PATCH DEC(0) +# endif +# if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +# define HOST_COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define HOST_SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define HOST_SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# if defined(__GNUC__) +# define HOST_SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# elif defined(__GNUG__) +# define HOST_SIMULATE_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define HOST_SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define HOST_SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || defined(__INTEL_LLVM_COMPILER) +# define HOST_COMPILER_ID "IntelLLVM" +#if defined(_MSC_VER) +# define HOST_SIMULATE_ID "MSVC" +#endif +#if defined(__GNUC__) +# define HOST_SIMULATE_ID "GNU" +#endif +/* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and + * later. Look for 6 digit vs. 8 digit version number to decide encoding. + * VVVV is no smaller than the current year when a version is released. + */ +#if __INTEL_LLVM_COMPILER < 1000000L +# define HOST_COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/100) +# define HOST_COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/10 % 10) +# define HOST_COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10) +#else +# define HOST_COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/10000) +# define HOST_COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/100 % 100) +# define HOST_COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100) +#endif +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define HOST_SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define HOST_SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#if defined(__GNUC__) +# define HOST_SIMULATE_VERSION_MAJOR DEC(__GNUC__) +#elif defined(__GNUG__) +# define HOST_SIMULATE_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +# define HOST_SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +# define HOST_SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif defined(__PATHCC__) +# define HOST_COMPILER_ID "PathScale" +# define HOST_COMPILER_VERSION_MAJOR DEC(__PATHCC__) +# define HOST_COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +# if defined(__PATHCC_PATCHLEVEL__) +# define HOST_COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +# endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +# define HOST_COMPILER_ID "Embarcadero" +# define HOST_COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) +# define HOST_COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) +# define HOST_COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +# define HOST_COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +# define HOST_COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) +# define HOST_COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +# define HOST_COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +# define HOST_COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define HOST_COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__WATCOMC__) +# define HOST_COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +# define HOST_COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +# define HOST_COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define HOST_COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__SUNPRO_CC) +# define HOST_COMPILER_ID "SunPro" +# if __SUNPRO_CC >= 0x5100 + /* __SUNPRO_CC = 0xVRRP */ +# define HOST_COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>12) +# define HOST_COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xFF) +# define HOST_COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# else + /* __SUNPRO_CC = 0xVRP */ +# define HOST_COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>8) +# define HOST_COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xF) +# define HOST_COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# endif + +#elif defined(__HP_aCC) +# define HOST_COMPILER_ID "HP" + /* __HP_aCC = VVRRPP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__HP_aCC/10000) +# define HOST_COMPILER_VERSION_MINOR DEC(__HP_aCC/100 % 100) +# define HOST_COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) + +#elif defined(__DECCXX) +# define HOST_COMPILER_ID "Compaq" + /* __DECCXX_VER = VVRRTPPPP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__DECCXX_VER/10000000) +# define HOST_COMPILER_VERSION_MINOR DEC(__DECCXX_VER/100000 % 100) +# define HOST_COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) + +#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) +# define HOST_COMPILER_ID "zOS" + /* __IBMCPP__ = VRP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define HOST_COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define HOST_COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__open_xl__) && defined(__clang__) +# define HOST_COMPILER_ID "IBMClang" +# define HOST_COMPILER_VERSION_MAJOR DEC(__open_xl_version__) +# define HOST_COMPILER_VERSION_MINOR DEC(__open_xl_release__) +# define HOST_COMPILER_VERSION_PATCH DEC(__open_xl_modification__) +# define HOST_COMPILER_VERSION_TWEAK DEC(__open_xl_ptf_fix_level__) +# define HOST_COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__ibmxl__) && defined(__clang__) +# define HOST_COMPILER_ID "XLClang" +# define HOST_COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +# define HOST_COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +# define HOST_COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +# define HOST_COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 +# define HOST_COMPILER_ID "XL" + /* __IBMCPP__ = VRP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define HOST_COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define HOST_COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 +# define HOST_COMPILER_ID "VisualAge" + /* __IBMCPP__ = VRP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define HOST_COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define HOST_COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__NVCOMPILER) +# define HOST_COMPILER_ID "NVHPC" +# define HOST_COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__) +# define HOST_COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__) +# if defined(__NVCOMPILER_PATCHLEVEL__) +# define HOST_COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__) +# endif + +#elif defined(__PGI) +# define HOST_COMPILER_ID "PGI" +# define HOST_COMPILER_VERSION_MAJOR DEC(__PGIC__) +# define HOST_COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define HOST_COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +# endif + +#elif defined(__clang__) && defined(__cray__) +# define HOST_COMPILER_ID "CrayClang" +# define HOST_COMPILER_VERSION_MAJOR DEC(__cray_major__) +# define HOST_COMPILER_VERSION_MINOR DEC(__cray_minor__) +# define HOST_COMPILER_VERSION_PATCH DEC(__cray_patchlevel__) +# define HOST_COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(_CRAYC) +# define HOST_COMPILER_ID "Cray" +# define HOST_COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +# define HOST_COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +# define HOST_COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) +# define HOST_COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) +# define HOST_COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__CLANG_FUJITSU) +# define HOST_COMPILER_ID "FujitsuClang" +# define HOST_COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define HOST_COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define HOST_COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# define HOST_COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__FUJITSU) +# define HOST_COMPILER_ID "Fujitsu" +# if defined(__FCC_version__) +# define HOST_COMPILER_VERSION __FCC_version__ +# elif defined(__FCC_major__) +# define HOST_COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define HOST_COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define HOST_COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# endif +# if defined(__fcc_version) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__fcc_version) +# elif defined(__FCC_VERSION) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION) +# endif + + +#elif defined(__ghs__) +# define HOST_COMPILER_ID "GHS" +/* __GHS_VERSION_NUMBER = VVVVRP */ +# ifdef __GHS_VERSION_NUMBER +# define HOST_COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +# define HOST_COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +# define HOST_COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +# endif + +#elif defined(__TASKING__) +# define HOST_COMPILER_ID "Tasking" + # define HOST_COMPILER_VERSION_MAJOR DEC(__VERSION__/1000) + # define HOST_COMPILER_VERSION_MINOR DEC(__VERSION__ % 100) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__VERSION__) + +#elif defined(__ORANGEC__) +# define HOST_COMPILER_ID "OrangeC" +# define HOST_COMPILER_VERSION_MAJOR DEC(__ORANGEC_MAJOR__) +# define HOST_COMPILER_VERSION_MINOR DEC(__ORANGEC_MINOR__) +# define HOST_COMPILER_VERSION_PATCH DEC(__ORANGEC_PATCHLEVEL__) + +#elif defined(__SCO_VERSION__) +# define HOST_COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +# define HOST_COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ + # define HOST_COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) + # define HOST_COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) + # define HOST_COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ + # define HOST_COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) + # define HOST_COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) + # define HOST_COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + + +#elif defined(__clang__) && defined(__apple_build_version__) +# define HOST_COMPILER_ID "AppleClang" +# if defined(_MSC_VER) +# define HOST_SIMULATE_ID "MSVC" +# endif +# define HOST_COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define HOST_COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define HOST_COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define HOST_SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define HOST_SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# define HOST_COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +# define HOST_COMPILER_ID "ARMClang" + # define HOST_COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000) + # define HOST_COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100) + # define HOST_COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION/100 % 100) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) && defined(__ti__) +# define HOST_COMPILER_ID "TIClang" + # define HOST_COMPILER_VERSION_MAJOR DEC(__ti_major__) + # define HOST_COMPILER_VERSION_MINOR DEC(__ti_minor__) + # define HOST_COMPILER_VERSION_PATCH DEC(__ti_patchlevel__) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__ti_version__) + +#elif defined(__clang__) +# define HOST_COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define HOST_SIMULATE_ID "MSVC" +# endif +# define HOST_COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define HOST_COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define HOST_COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define HOST_SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define HOST_SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + +#elif defined(__LCC__) && (defined(__GNUC__) || defined(__GNUG__) || defined(__MCST__)) +# define HOST_COMPILER_ID "LCC" +# define HOST_COMPILER_VERSION_MAJOR DEC(__LCC__ / 100) +# define HOST_COMPILER_VERSION_MINOR DEC(__LCC__ % 100) +# if defined(__LCC_MINOR__) +# define HOST_COMPILER_VERSION_PATCH DEC(__LCC_MINOR__) +# endif +# if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define HOST_SIMULATE_ID "GNU" +# define HOST_SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# define HOST_SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# if defined(__GNUC_PATCHLEVEL__) +# define HOST_SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif +# endif + +#elif defined(__GNUC__) || defined(__GNUG__) +# define HOST_COMPILER_ID "GNU" +# if defined(__GNUC__) +# define HOST_COMPILER_VERSION_MAJOR DEC(__GNUC__) +# else +# define HOST_COMPILER_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define HOST_COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define HOST_COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(_MSC_VER) +# define HOST_COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +# define HOST_COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +# define HOST_COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +# if defined(_MSC_FULL_VER) +# if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +# define HOST_COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +# else + /* _MSC_FULL_VER = VVRRPPPP */ +# define HOST_COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +# endif +# endif +# if defined(_MSC_BUILD) +# define HOST_COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +# endif + +#elif defined(_ADI_COMPILER) +# define HOST_COMPILER_ID "ADSP" +#if defined(__VERSIONNUM__) + /* __VERSIONNUM__ = 0xVVRRPPTT */ +# define HOST_COMPILER_VERSION_MAJOR DEC(__VERSIONNUM__ >> 24 & 0xFF) +# define HOST_COMPILER_VERSION_MINOR DEC(__VERSIONNUM__ >> 16 & 0xFF) +# define HOST_COMPILER_VERSION_PATCH DEC(__VERSIONNUM__ >> 8 & 0xFF) +# define HOST_COMPILER_VERSION_TWEAK DEC(__VERSIONNUM__ & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# define HOST_COMPILER_ID "IAR" +# if defined(__VER__) && defined(__ICCARM__) +# define HOST_COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +# define HOST_COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +# define HOST_COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__)) +# define HOST_COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +# define HOST_COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100)) +# define HOST_COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +# define HOST_COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# endif + + +#endif +#endif /* __NVCC__ */ + + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; +#ifdef SIMULATE_ID +char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; +#endif + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +/* Identify known platforms by name. */ +#if defined(__linux) || defined(__linux__) || defined(linux) +# define PLATFORM_ID "Linux" + +#elif defined(__MSYS__) +# define PLATFORM_ID "MSYS" + +#elif defined(__CYGWIN__) +# define PLATFORM_ID "Cygwin" + +#elif defined(__MINGW32__) +# define PLATFORM_ID "MinGW" + +#elif defined(__APPLE__) +# define PLATFORM_ID "Darwin" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# define PLATFORM_ID "Windows" + +#elif defined(__FreeBSD__) || defined(__FreeBSD) +# define PLATFORM_ID "FreeBSD" + +#elif defined(__NetBSD__) || defined(__NetBSD) +# define PLATFORM_ID "NetBSD" + +#elif defined(__OpenBSD__) || defined(__OPENBSD) +# define PLATFORM_ID "OpenBSD" + +#elif defined(__sun) || defined(sun) +# define PLATFORM_ID "SunOS" + +#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) +# define PLATFORM_ID "AIX" + +#elif defined(__hpux) || defined(__hpux__) +# define PLATFORM_ID "HP-UX" + +#elif defined(__HAIKU__) +# define PLATFORM_ID "Haiku" + +#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) +# define PLATFORM_ID "BeOS" + +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_ID "QNX" + +#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) +# define PLATFORM_ID "Tru64" + +#elif defined(__riscos) || defined(__riscos__) +# define PLATFORM_ID "RISCos" + +#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) +# define PLATFORM_ID "SINIX" + +#elif defined(__UNIX_SV__) +# define PLATFORM_ID "UNIX_SV" + +#elif defined(__bsdos__) +# define PLATFORM_ID "BSDOS" + +#elif defined(_MPRAS) || defined(MPRAS) +# define PLATFORM_ID "MP-RAS" + +#elif defined(__osf) || defined(__osf__) +# define PLATFORM_ID "OSF1" + +#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) +# define PLATFORM_ID "SCO_SV" + +#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) +# define PLATFORM_ID "ULTRIX" + +#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) +# define PLATFORM_ID "Xenix" + +#elif defined(__WATCOMC__) +# if defined(__LINUX__) +# define PLATFORM_ID "Linux" + +# elif defined(__DOS__) +# define PLATFORM_ID "DOS" + +# elif defined(__OS2__) +# define PLATFORM_ID "OS2" + +# elif defined(__WINDOWS__) +# define PLATFORM_ID "Windows3x" + +# elif defined(__VXWORKS__) +# define PLATFORM_ID "VxWorks" + +# else /* unknown platform */ +# define PLATFORM_ID +# endif + +#elif defined(__INTEGRITY) +# if defined(INT_178B) +# define PLATFORM_ID "Integrity178" + +# else /* regular Integrity */ +# define PLATFORM_ID "Integrity" +# endif + +# elif defined(_ADI_COMPILER) +# define PLATFORM_ID "ADSP" + +#else /* unknown platform */ +# define PLATFORM_ID + +#endif + +/* For windows compilers MSVC and Intel we can determine + the architecture of the compiler being used. This is because + the compilers do not have flags that can change the architecture, + but rather depend on which compiler is being used +*/ +#if defined(_WIN32) && defined(_MSC_VER) +# if defined(_M_IA64) +# define ARCHITECTURE_ID "IA64" + +# elif defined(_M_ARM64EC) +# define ARCHITECTURE_ID "ARM64EC" + +# elif defined(_M_X64) || defined(_M_AMD64) +# define ARCHITECTURE_ID "x64" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# elif defined(_M_ARM64) +# define ARCHITECTURE_ID "ARM64" + +# elif defined(_M_ARM) +# if _M_ARM == 4 +# define ARCHITECTURE_ID "ARMV4I" +# elif _M_ARM == 5 +# define ARCHITECTURE_ID "ARMV5I" +# else +# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) +# endif + +# elif defined(_M_MIPS) +# define ARCHITECTURE_ID "MIPS" + +# elif defined(_M_SH) +# define ARCHITECTURE_ID "SHx" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__WATCOMC__) +# if defined(_M_I86) +# define ARCHITECTURE_ID "I86" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# if defined(__ICCARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__ICCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__ICCRH850__) +# define ARCHITECTURE_ID "RH850" + +# elif defined(__ICCRL78__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__ICCRISCV__) +# define ARCHITECTURE_ID "RISCV" + +# elif defined(__ICCAVR__) +# define ARCHITECTURE_ID "AVR" + +# elif defined(__ICC430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__ICCV850__) +# define ARCHITECTURE_ID "V850" + +# elif defined(__ICC8051__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__ICCSTM8__) +# define ARCHITECTURE_ID "STM8" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__ghs__) +# if defined(__PPC64__) +# define ARCHITECTURE_ID "PPC64" + +# elif defined(__ppc__) +# define ARCHITECTURE_ID "PPC" + +# elif defined(__ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__x86_64__) +# define ARCHITECTURE_ID "x64" + +# elif defined(__i386__) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__clang__) && defined(__ti__) +# if defined(__ARM_ARCH) +# define ARCHITECTURE_ID "ARM" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__TI_COMPILER_VERSION__) +# if defined(__TI_ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__MSP430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__TMS320C28XX__) +# define ARCHITECTURE_ID "TMS320C28x" + +# elif defined(__TMS320C6X__) || defined(_TMS320C6X) +# define ARCHITECTURE_ID "TMS320C6x" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +# elif defined(__ADSPSHARC__) +# define ARCHITECTURE_ID "SHARC" + +# elif defined(__ADSPBLACKFIN__) +# define ARCHITECTURE_ID "Blackfin" + +#elif defined(__TASKING__) + +# if defined(__CTC__) || defined(__CPTC__) +# define ARCHITECTURE_ID "TriCore" + +# elif defined(__CMCS__) +# define ARCHITECTURE_ID "MCS" + +# elif defined(__CARM__) || defined(__CPARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__CARC__) +# define ARCHITECTURE_ID "ARC" + +# elif defined(__C51__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__CPCP__) +# define ARCHITECTURE_ID "PCP" + +# else +# define ARCHITECTURE_ID "" +# endif + +#else +# define ARCHITECTURE_ID +#endif + +/* Convert integer to decimal digit literals. */ +#define DEC(n) \ + ('0' + (((n) / 10000000)%10)), \ + ('0' + (((n) / 1000000)%10)), \ + ('0' + (((n) / 100000)%10)), \ + ('0' + (((n) / 10000)%10)), \ + ('0' + (((n) / 1000)%10)), \ + ('0' + (((n) / 100)%10)), \ + ('0' + (((n) / 10)%10)), \ + ('0' + ((n) % 10)) + +/* Convert integer to hex digit literals. */ +#define HEX(n) \ + ('0' + ((n)>>28 & 0xF)), \ + ('0' + ((n)>>24 & 0xF)), \ + ('0' + ((n)>>20 & 0xF)), \ + ('0' + ((n)>>16 & 0xF)), \ + ('0' + ((n)>>12 & 0xF)), \ + ('0' + ((n)>>8 & 0xF)), \ + ('0' + ((n)>>4 & 0xF)), \ + ('0' + ((n) & 0xF)) + +/* Construct a string literal encoding the version number. */ +#ifdef COMPILER_VERSION +char const* info_version = "INFO" ":" "compiler_version[" COMPILER_VERSION "]"; + +/* Construct a string literal encoding the version number components. */ +#elif defined(COMPILER_VERSION_MAJOR) +char const info_version[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + COMPILER_VERSION_MAJOR, +# ifdef COMPILER_VERSION_MINOR + '.', COMPILER_VERSION_MINOR, +# ifdef COMPILER_VERSION_PATCH + '.', COMPILER_VERSION_PATCH, +# ifdef COMPILER_VERSION_TWEAK + '.', COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct a string literal encoding the internal version number. */ +#ifdef COMPILER_VERSION_INTERNAL +char const info_version_internal[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_', + 'i','n','t','e','r','n','a','l','[', + COMPILER_VERSION_INTERNAL,']','\0'}; +#elif defined(COMPILER_VERSION_INTERNAL_STR) +char const* info_version_internal = "INFO" ":" "compiler_version_internal[" COMPILER_VERSION_INTERNAL_STR "]"; +#endif + +/* Construct a string literal encoding the version number components. */ +#ifdef SIMULATE_VERSION_MAJOR +char const info_simulate_version[] = { + 'I', 'N', 'F', 'O', ':', + 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', + SIMULATE_VERSION_MAJOR, +# ifdef SIMULATE_VERSION_MINOR + '.', SIMULATE_VERSION_MINOR, +# ifdef SIMULATE_VERSION_PATCH + '.', SIMULATE_VERSION_PATCH, +# ifdef SIMULATE_VERSION_TWEAK + '.', SIMULATE_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; +char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; + + + +#ifdef HOST_COMPILER_ID +char const* info_host_compiler = "INFO" ":" "host_compiler[" HOST_COMPILER_ID "]"; +#endif +#ifdef HOST_COMPILER_VERSION +char const* info_host_compiler_version = "INFO" ":" "host_compiler_version[" HOST_COMPILER_VERSION "]"; +#elif defined(HOST_COMPILER_VERSION_MAJOR) +char const info_host_compiler_version[] = { + 'I', 'N', 'F', 'O', ':','h','o','s','t','_', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + HOST_COMPILER_VERSION_MAJOR, +# ifdef HOST_COMPILER_VERSION_MINOR + '.', HOST_COMPILER_VERSION_MINOR, +# ifdef HOST_COMPILER_VERSION_PATCH + '.', HOST_COMPILER_VERSION_PATCH, +# ifdef HOST_COMPILER_VERSION_TWEAK + '.', HOST_COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +#define CXX_STD_11 201103L +#define CXX_STD_14 201402L +#define CXX_STD_17 201703L +#define CXX_STD_20 202002L +#define CXX_STD_23 202302L + +#if defined(_MSC_VER) && defined(_MSVC_LANG) +# if _MSVC_LANG > __cplusplus +# define CXX_STD _MSVC_LANG +# else +# define CXX_STD __cplusplus +# endif +#else +# define CXX_STD __cplusplus +#endif + +const char* info_language_standard_default = "INFO" ":" "standard_default[" +#if CXX_STD > CXX_STD_23 + "26" +#elif CXX_STD > CXX_STD_20 + "23" +#elif CXX_STD > CXX_STD_17 + "20" +#elif CXX_STD > CXX_STD_14 + "17" +#elif CXX_STD > CXX_STD_11 + "14" +#elif CXX_STD >= CXX_STD_11 + "11" +#else + "03" +#endif +"]"; + +const char* info_language_extensions_default = "INFO" ":" "extensions_default[" +#if (defined(__clang__) || defined(__GNUC__)) && \ + !defined(__STRICT_ANSI__) + "ON" +#else + "OFF" +#endif +"]"; + +/*--------------------------------------------------------------------------*/ + +int main(int argc, char* argv[]) +{ + int require = 0; + require += info_compiler[argc]; + require += info_platform[argc]; +#ifdef COMPILER_VERSION_MAJOR + require += info_version[argc]; +#endif +#ifdef SIMULATE_ID + require += info_simulate[argc]; +#endif +#ifdef SIMULATE_VERSION_MAJOR + require += info_simulate_version[argc]; +#endif +#ifdef HOST_COMPILER_ID + require += info_host_compiler[argc]; +#endif +#ifdef HOST_COMPILER_VERSION_MAJOR + require += info_host_compiler_version[argc]; +#endif + require += info_language_standard_default[argc]; + require += info_language_extensions_default[argc]; + (void)argv; + return require; +} diff --git a/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/a.out b/src/cuda/HPC/graph/mst_standalone/build/CMakeFiles/4.0.0/CompilerIdCUDA/a.out new file mode 100755 index 0000000000000000000000000000000000000000..ffb36347365af49f1b84d33d5d46299ad387c0ef GIT binary patch literal 1003384 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
svWtoX<_>(RG|)P9p$x&5QvgU7e! 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

NI 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