CUDA Host-to-Device Roundtrip
Problem Statement
Square every element on the GPU: out = x * x. The point is the full memory roundtrip β copy the input host array to the device, compute, then copy the result back.
Background
cuda.to_device(host_array) allocates device memory and copies the data up; device_array.copy_to_host() brings it back. The kernel only ever touches device memory.
Your Task
Implement square_kernel and run(n=1024) returning whether the device result matches x * x.
How it is tested
Your solution must define a top-level function run(...) that allocates the inputs, copies them to the GPU, launches your @cuda.jit kernel, and returns a Python bool from np.allclose(gpu_result, reference). The grader prints run(...); the expected output is True.
Example:
n = 1024
True
- The input
n = 1024is used to create a host array of size 1024, which is then copied to the device usingcuda.to_device(). - The
square_kernelfunction is launched on the GPU, which squares every element in the device array: out=xβx. - The result is then copied back to the host using
device_array.copy_to_host()and compared to the reference result, which is also obtained by squaring the original host array: reference=xβx. - The comparison is done using
np.allclose(), which checks if the two arrays are element-wise equal within a tolerance, resulting in the outputTrueif they match.
Constraints:
- Use cuda.to_device for the input and copy_to_host for the output
- out[i] = x[i] * x[i]
- Bounds-check the global index
Background Knowledge
Introduction to CUDA
CUDA is a parallel computing platform and programming model developed by NVIDIA. It allows developers to harness the power of GPU (Graphics Processing Unit) to perform computationally intensive tasks. In the context of this problem, we will be using CUDA to perform a simple operation on the GPU: squaring every element of an input array.
Memory Management in CUDA
In CUDA, memory management is crucial for efficient computation. The host (CPU) and device (GPU) have separate memory spaces. To perform computations on the GPU, data must be transferred from the host to the device using functions like cuda.to_device(). Similarly, results must be transferred back from the device to the host using methods like device_array.copy_to_host(). Understanding this memory roundtrip is essential for solving this problem.
CUDA Kernels
A CUDA kernel is a function that runs on the GPU. Kernels are launched from the host and execute in parallel across the device. In this problem, we will define a kernel function square_kernel that takes an input array, squares each element, and stores the result in an output array. The kernel will be launched using the @cuda.jit decorator, which compiles the function for execution on the GPU.
Algorithm/Approach
The general approach to solving this problem involves:
- Allocating memory on the host and device for the input and output arrays
- Copying the input array from the host to the device
- Launching the square_kernel function to perform the computation on the GPU
- Copying the result from the device back to the host
- Verifying that the result matches the expected output (i.e., the squared input array)
Step-by-Step Strategy
To implement the solution:
- Import necessary libraries, including numba for CUDA support and numpy for array operations.
- Define the square_kernel function, which will take an input array and an output array as arguments.
- Allocate memory on the host for the input array and initialize it with some values.
- Copy the input array from the host to the device using cuda.to_device().
- Launch the square_kernel function on the GPU, passing the input and output arrays as arguments.
- Copy the result from the device back to the host using device_array.copy_to_host().
- Verify that the result matches the expected output using np.allclose().
Common Pitfalls
When implementing the solution, watch out for:
- Incorrect memory allocation or deallocation, which can lead to memory leaks or crashes.
- Failure to synchronize the host and device, which can result in incorrect or incomplete results.
- Insufficient error checking, which can make it difficult to diagnose issues.
Time & Space Complexity
The time complexity of the solution will depend on the size of the input array and the number of threads launched on the GPU. In general, the time complexity will be O(n), where n is the size of the input array. The space complexity will also be O(n), as we need to allocate memory for the input and output arrays on both the host and device.