--- description: Enforces CUDA coding standards, performance optimizations, and best practices to ensure efficient and maintainable GPU-accelerated code. This rule provides guidance on code organization, memory management, error handling, and more. globs: *.cu --- - # CUDA Best Practices Guide This document outlines best practices for CUDA development, focusing on code organization, performance optimization, and common pitfalls. It is based on NVIDIA's CUDA C++ Best Practices Guide and expands on it with more detailed recommendations. - ## 1. Code Organization and Structure - ### 1.1 Directory Structure - Organize your CUDA project with a clear directory structure. A common structure includes: project_root/ ├── src/ │ ├── kernels/ # CUDA kernel source files (.cu) │ ├── host/ # Host-side code (.cpp, .h) │ ├── common/ # Shared utility code (.h, .cpp) │ └── include/ # Header files ├── build/ # Build output directory ├── data/ # Input and output data ├── tests/ # Unit and integration tests └── CMakeLists.txt # CMake build configuration - ### 1.2 File Naming Conventions - Use descriptive file names that clearly indicate the purpose of the file. - Kernel files: `kernel_name.cu` (e.g., `matrix_multiply.cu`) - Host files: `module_name.cpp`, `module_name.h` (e.g., `data_loader.cpp`, `data_loader.h`) - Common files: `utility.h`, `error_handling.cpp` - ### 1.3 Module Organization - Divide your code into logical modules based on functionality. - Use namespaces to avoid naming conflicts and improve code organization. - Encapsulate CUDA kernel launches within well-defined functions or classes. - ### 1.4 Component Architecture - Design your application with a modular component architecture to facilitate code reuse and maintainability. - Decouple host-side code from CUDA kernels as much as possible. - Use abstraction layers to hide CUDA-specific details from higher-level components. - ### 1.5 Code Splitting Strategies - Split large CUDA kernels into smaller, more manageable functions. - Use separate files for different kernels or related functionalities. - Consider using template metaprogramming to generate specialized kernels at compile time. - ## 2. Common Patterns and Anti-patterns - ### 2.1 Design Patterns Specific to CUDA - **CUDA Stream Pattern:** Use CUDA streams to overlap data transfers and kernel execution. - **Memory Pooling Pattern:** Implement memory pools to reduce the overhead of frequent memory allocations and deallocations. - **Tiling Pattern:** Divide large data structures into smaller tiles to improve data locality and cache utilization. - **Reduction Pattern:** Use parallel reduction algorithms to efficiently compute aggregate values. - ### 2.2 Recommended Approaches for Common Tasks - **Error Handling:** Use the CUDA error handling API to check for errors after each CUDA function call. - **Memory Allocation:** Use `cudaMalloc`, `cudaFree`, and related functions for memory allocation on the device. - **Data Transfer:** Use `cudaMemcpy` to transfer data between host and device memory. - **Kernel Launch:** Use the `<<>>` syntax to launch CUDA kernels. - ### 2.3 Anti-patterns and Code Smells to Avoid - **Synchronous Memory Transfers:** Avoid blocking memory transfers that stall the GPU. - **Excessive Global Memory Access:** Minimize global memory access by using shared memory and registers. - **Thread Divergence:** Avoid conditional branches that cause threads within a warp to execute different code paths. - **Uncoalesced Memory Access:** Ensure that threads access memory in a coalesced manner to maximize memory bandwidth. - **CPU-GPU Synchronization Bottlenecks:** Minimize the number of synchronization points between the CPU and GPU. - ### 2.4 State Management Best Practices - Encapsulate CUDA context and device management within a dedicated class or module. - Avoid global state variables that can lead to unexpected behavior and concurrency issues. - Use RAII (Resource Acquisition Is Initialization) to ensure that CUDA resources are properly released. - ### 2.5 Error Handling Patterns - Check the return value of every CUDA function call and handle errors appropriately. - Use `cudaGetLastError` to retrieve the last error that occurred on the device. - Implement custom error handling routines for specific error conditions. - Log error messages with file name, line number, and a descriptive error message. - ## 3. Performance Considerations - ### 3.1 Optimization Techniques - **Kernel Fusion:** Combine multiple kernels into a single kernel to reduce kernel launch overhead and data transfers. - **Loop Unrolling:** Unroll loops to improve instruction-level parallelism. - **Instruction Scheduling:** Optimize instruction scheduling to reduce pipeline stalls. - **Constant Memory Usage:** Store frequently accessed read-only data in constant memory. - **Texture Memory Usage:** Utilize texture memory for spatially coherent data access patterns. - ### 3.2 Memory Management - **Minimize Data Transfers:** Reduce the amount of data transferred between host and device. - **Asynchronous Data Transfers:** Use asynchronous memory transfers with CUDA streams to overlap computation and communication. - **Zero-Copy Memory:** Use zero-copy memory to directly access host memory from the GPU (use with caution due to performance implications). - **Pinned Memory (Page-Locked Memory):** Use pinned memory for efficient asynchronous data transfers. - ### 3.3 CUDA Profiler - Use the NVIDIA Nsight Systems and Nsight Compute profilers to identify performance bottlenecks. - ## 4. Security Best Practices - ### 4.1 Common Vulnerabilities and How to Prevent Them - **Buffer Overflows:** Carefully validate input sizes to prevent buffer overflows in CUDA kernels. - **Integer Overflows:** Check for potential integer overflows in calculations involving data sizes and indices. - **Race Conditions:** Protect shared data with appropriate synchronization mechanisms (e.g., atomic operations, mutexes) to prevent race conditions. - **Injection Attacks:** Sanitize input data to prevent injection attacks that could execute arbitrary code on the GPU. - ### 4.2 Input Validation - Validate all input data received by CUDA kernels to ensure that it is within the expected range and format. - Check for invalid or malicious input that could lead to security vulnerabilities. - ### 4.3 Data Protection Strategies - Encrypt sensitive data stored on the GPU to protect it from unauthorized access. - Use secure communication channels to transfer data between host and device. - ## 5. Testing Approaches - ### 5.1 Unit Testing Strategies - Write unit tests to verify the correctness of individual CUDA kernels and host-side functions. - Use a testing framework like Google Test or Catch2 to automate the testing process. - Mock CUDA runtime functions to isolate kernels during testing. - Use a separate compilation approach to test individual kernel functions. - ### 5.2 Integration Testing - Perform integration tests to verify the interaction between different components of the CUDA application. - Test data transfers between host and device, kernel launches, and error handling. - ### 5.3 Test Organization - Organize your tests into a logical directory structure. - Use descriptive test names that clearly indicate the purpose of each test. - Group related tests together into test suites. - ### 5.4 Mocking and Stubbing - Use mocking and stubbing techniques to isolate components during testing and simulate different scenarios. - Mock CUDA runtime functions to control the behavior of the GPU during testing. - ## 6. Common Pitfalls and Gotchas - ### 6.1 Frequent Mistakes Developers Make - **Ignoring CUDA Error Codes:** Always check the return values of CUDA functions to ensure that they succeed. - **Incorrect Grid and Block Dimensions:** Choose appropriate grid and block dimensions for your kernels. - **Shared Memory Bank Conflicts:** Avoid shared memory bank conflicts to maximize memory bandwidth. - **Thread Divergence:** Minimize thread divergence within warps to improve performance. - **Uncoalesced Memory Access:** Ensure that threads access memory in a coalesced manner. - ### 6.2 Version-Specific Issues - Be aware of compatibility issues between different CUDA versions. - Use conditional compilation to handle version-specific code. - ### 6.3 Debugging Strategies - Use the NVIDIA Nsight Systems and Nsight Compute debuggers to debug CUDA code. - Insert print statements to track the execution flow and variable values. - Use the `cudaDeviceSynchronize` function to force the GPU to complete all operations before proceeding. - ## 7. Tooling and Environment - ### 7.1 Recommended Development Tools - **CUDA Toolkit:** Install the latest version of the CUDA Toolkit from NVIDIA's website. - **NVIDIA Nsight Systems and Nsight Compute:** Use the Nsight profilers to analyze and optimize CUDA code. - **CMake:** Use CMake to manage the build process. - **Integrated Development Environment (IDE):** Use an IDE such as Visual Studio or Eclipse with CUDA support. - ### 7.2 Build Configuration - Use CMake to generate build files for your target platform. - Configure the CUDA compiler (nvcc) with appropriate optimization flags. - ### 7.3 Linting and Formatting - Use a linter such as clang-tidy to enforce coding standards and identify potential errors. - Use a code formatter such as clang-format to ensure consistent code formatting.