Skip to main content
rule

cuda — Cursor rule: CUDA C++ conventions for AI coding

A Cursor rule that tells the agent how to write CUDA C++: error-checked API calls, host/device separation, kernel tuning. Useful when silent GPU failures eat your afternoon.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/cuda.mdc
Open on GitHub

What cuda does

Drop the rule into .cursor/rules/cuda.mdc and it applies to every file in the project. It sets naming and structure: wrap CUDA API calls in an error-checking macro, keep host orchestration apart from device computation, suffix __global__ functions with _kernel, __device__ functions with _device, and prefix shared memory with smem_.

Memory and throughput get their own section. RAII wrappers for device buffers and streams instead of hand-written cudaFree, cudaMallocPitch for 2D arrays, cudaMemcpyAsync across multiple streams so transfers overlap with compute. For common math the rule points at CUDA-X libraries — cuBLAS, cuFFT, cuDNN, cuML — rather than a hand-rolled GEMM.

Data layout matters too: structures-of-arrays instead of an array of structs, const and __restrict__ qualifiers to help the compiler, warp divergence reduced with predicates or lookup tables. The file is written for Cursor. Claude, Codex and Copilot use different paths, so the same text has to be copied there before it does anything.

What it helps with

  • catch a CUDA API error at the call site instead of a later crash
  • split host orchestration from device kernels across files
  • stop leaking device memory with RAII wrappers
  • hide host-to-device copy latency behind compute
  • align 2D matrix rows for coalesced access

Example prompts for the agent

  • Refactor this kernel to cut warp divergence and add CUDA_CHECK around every API call
  • Rewrite the device allocation as an RAII wrapper and move host and device code into separate files
  • Replace my hand-written matrix multiply with a cuBLAS call

How to install cuda

Manually: put the files in place

.cursor/rules/cuda.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about cuda

How do I install the cuda rule in Cursor?

Create .cursor/rules/cuda.mdc in your project root and paste the rule text into it. Cursor picks it up automatically for files matching **/*.

What does the cuda rule do?

It gives the agent CUDA C++ conventions: error-checked API calls, host/device separation, naming for kernels and device functions, memory and stream handling, and data access patterns.

Does it work with Claude or GitHub Copilot?

The text is the same but the paths differ: .claude/rules/cuda.md for Claude and .github/instructions/cuda.instructions.md for GitHub Copilot. You place the file once per platform.

Source text

cuda.mdc · CC0-1.0
---
description: Definitive guidelines for writing high-performance, maintainable, and error-resistant CUDA C++ code, focusing on modern practices, memory management, kernel optimization, and robust error handling.
globs: **/*
---
# CUDA Best Practices

This guide outlines the essential practices for developing efficient and maintainable CUDA C++ applications. Adhere to these rules to maximize GPU throughput, reduce debugging time, and ensure code quality across projects.

## 1. Code Organization and Structure

### 1.1 Robust Error Handling
**Always wrap CUDA API calls in an error-checking macro.** This prevents silent failures and provides immediate, actionable debugging information.

❌ BAD:
```cpp
cudaMalloc(&d_data, size);
// ... potentially crash later without knowing why
```

✅ GOOD:
```cpp
#define CUDA_CHECK(call)                                                          \
    do {                                                                          \
        cudaError_t err = call;                                                   \
        if (err != cudaSuccess) {                                                 \
            fprintf(stderr, "CUDA Error: %s:%d: %s\n", __FILE__, __LINE__, cudaGetErrorString(err)); \
            exit(EXIT_FAILURE);                                                   \
        }                                                                         \
    } while (0)

// Usage:
CUDA_CHECK(cudaMalloc(&d_data, size));
kernel<<<grid, block>>>(d_data);
CUDA_CHECK(cudaGetLastError()); // Check for kernel launch errors
```

### 1.2 Host-Device Separation
Clearly separate host (CPU) orchestration logic from device (GPU) computation.

❌ BAD:
```cpp
// host_code.cu
void processData() {
    cudaMalloc(&d_data, size); // Mixed host/device
    kernel<<<...>>>(d_data);
}
```

✅ GOOD:
```cpp
// host_manager.cpp
void allocateAndLaunch(float* h_in, int N) {
    float *d_in;
    CUDA_CHECK(cudaMalloc((void**)&d_in, N * sizeof(float)));
    CUDA_CHECK(cudaMemcpy(d_in, h_in, N * sizeof(float), cudaMemcpyHostToDevice));
    myKernel_kernel<<<N/256 + 1, 256>>>(d_in, N);
    CUDA_CHECK(cudaGetLastError());
    CUDA_CHECK(cudaFree(d_in));
}

// device_kernels.cu
__global__ void myKernel_kernel(float* d_in, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) { d_in[idx] *= 2.0f; }
}
```

### 1.3 Consistent Naming Conventions
Use clear suffixes and prefixes to distinguish CUDA components.
*   `_kernel`: For `__global__` functions.
*   `_device`: For `__device__` functions.
*   `smem_`: For `__shared__` variables.
*   `k` prefix: For `__constant__` memory or compile-time constants.

❌ BAD:
```cpp
void compute(float* data); // Ambiguous
__shared__ int temp;
```

✅ GOOD:
```cpp
__global__ void myCompute_kernel(float* d_data);
__device__ float calculate_device(float val);
extern __shared__ float smem_scratch[];
__constant__ const int kTileSize = 32;
```

### 1.4 Modularity
Decompose large kernels into smaller, focused `__device__` functions.

❌ BAD:
```cpp
__global__ void monolithicKernel(float* d_data, int N) { /* Do everything here */ }
```

✅ GOOD:
```cpp
__device__ float processElement_device(float val) { return val * val + 1.0f; }

__global__ void modularKernel(float* d_in, int N) {
    int global_idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (global_idx < N) {
        d_in[global_idx] = processElement_device(d_in[global_idx]);
    }
}
```

## 2. Common Patterns and Anti-patterns

### 2.1 Modern Memory Management (RAII)
Use RAII wrappers for device memory and streams to prevent leaks.

❌ BAD:
```cpp
float* d_data; cudaMalloc(&d_data, size); // ... cudaFree(d_data); // Easy to forget
```

✅ GOOD:
```cpp
// Use a robust library or std::unique_ptr with custom deleter in production.
template<typename T> struct CudaDeviceBuffer {
    T* ptr = nullptr; size_t count = 0;
    CudaDeviceBuffer(size_t n) : count(n) { CUDA_CHECK(cudaMalloc(&ptr, n * sizeof(T))); }
    ~CudaDeviceBuffer() { if (ptr) CUDA_CHECK(cudaFree(ptr)); }
    T* get() { return ptr; }
};
// Usage: CudaDeviceBuffer<float> d_data(N); // Auto-freed on scope exit
```

### 2.2 Aligned Memory Allocation
For 2D arrays, use `cudaMallocPitch` for proper alignment and coalescing.

❌ BAD:
```cpp
cudaMalloc(&d_matrix, width * height * sizeof(float)); // Misaligned rows possible
```

✅ GOOD:
```cpp
float* d_matrix; size_t pitch; // Stored in bytes
CUDA_CHECK(cudaMallocPitch((void**)&d_matrix, &pitch, width * sizeof(float), height));
// Access in kernel: d_matrix[row * (pitch / sizeof(float)) + col];
```

### 2.3 Overlap Data Transfer and Computation
Utilize `cudaMemcpyAsync` with multiple streams to hide data transfer latency.

❌ BAD:
```cpp
cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice); // Synchronous
kernel<<<...>>>(d_data);
```

✅ GOOD:
```cpp
cudaStream_t stream1, stream2;
CUDA_CHECK(cudaStreamCreate(&stream1));
// Segment 1: Transfer H->D, Compute, Transfer D->H
CUDA_CHECK(cudaMemcpyAsync(d_data_seg1, h_data_seg1, size_seg1, cudaMemcpyHostToDevice, stream1));
kernel_seg1<<<...>>>(d_data_seg1, stream1);
CUDA_CHECK(cudaMemcpyAsync(h_result_seg1, d_result_seg1, size_seg1, cudaMemcpyDeviceToHost, stream1));
// ... (similar for stream2) ...
CUDA_CHECK(cudaStreamSynchronize(stream1));
CUDA_CHECK(cudaStreamDestroy(stream1));
```

### 2.4 Minimize Warp Divergence
Refactor conditional logic to reduce divergent branches within a warp. Use lookup tables or predicate execution.

❌ BAD:
```cpp
if (idx % 2 == 0) { d_data[idx] *= 2; } else { d_data[idx] += 1; }
```

✅ GOOD:
```cpp
int multiplier = (idx % 2 == 0) ? 2 : 1;
int adder = (idx % 2 == 0) ? 0 : 1;
d_data[idx] = d_data[idx] * multiplier + adder;
```

### 2.5 Use `__restrict__` and `const` Qualifiers
Enable compiler optimizations by explicitly declaring non-aliased pointers and read-only data.

❌ BAD:
```cpp
__global__ void add(float* a, float* b, float* c, int N) { /* ... */ }
```

✅ GOOD:
```cpp
__global__ void add(const float* __restrict__ a, const float* __restrict__ b, float* __restrict__ c, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) { c[idx] = a[idx] + b[idx]; }
}
```

### 2.6 Data Layout: Structures-of-Arrays (SoA)
Organize data as SoA for better memory coalescing, especially for frequently accessed fields.

❌ BAD:
```cpp
struct Particle { float x, y, z; }; Particle* particles = new Particle[N];
```

✅ GOOD:
```cpp
struct ParticlesSoA { float *x, *y, *z; }; // Allocate each array separately
```

### 2.7 Leverage CUDA-X Libraries
For common operations (BLAS, FFT, DNN), use highly optimized CUDA-X libraries (cuBLAS, cuFFT, cuDNN, cuML).

❌ BAD:
```cpp
__global__ void myMatrixMul_kernel(...) { /* complex GEMM logic */ }
```

✅ GOOD:
```cpp
cublasHandle_t handle; CUDA_CHECK(cublasCreate(&handle));
// ... setup matrices ...
CUDA_CHECK(cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, M, N, K, &alpha, d_A, M, d_B, K, &beta, d_C, M));
CUDA_CHECK(cublasDestroy(handle));
```

### 2.8 Embrace CUDA Tile (Toolkit 13.1+)
For tile-based tensor operations, use the new CUDA Tile programming model and cuTile Python API. This simplifies complex tiling logic and targets specialized hardware.

✅ GOOD:
```cpp
// Refer to NVIDIA documentation for specific CUDA Tile API usage.
// This abstracts away explicit shared memory management and synchronization for tiling.
// E.g., using cuTile Python API to express high-level tensor operations.
```

## 3. Performance Considerations

### 3.1 Memory Coalescing
Ensure global memory accesses by threads within a warp are contiguous and aligned.

❌ BAD:
```cpp
d_matrix[threadIdx.x * N + blockIdx.x]; // Strided access
```

✅ GOOD:
```cpp
d_matrix[blockIdx.x * blockDim.x + threadIdx.x]; // Coalesced access
```

### 3.2 Shared Memory Optimization
Use shared memory to cache frequently accessed global memory data. Be mindful of bank conflicts.

❌ BAD:
```cpp
__shared__ float smem_data[256]; float val = smem_data[threadIdx.x * 2]; // Bank conflicts
```

✅ GOOD:
```cpp
__shared__ float smem_data[256]; float val = smem_data[threadIdx.x]; // No bank conflicts
```

### 3.3 Constant Memory
Use `__constant__` memory for read-only data uniform across all threads. It has a fast, cached access path.

✅ GOOD:
```cpp
__constant__ float kCoefficients[10];
__global__ void process_kernel(float* d_data) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) { d_data[idx] *= kCoefficients[0]; } // Fast access
}
```

## 4. Common Pitfalls and Gotchas

### 4.1 Unchecked API Calls
As covered in 1.1, neglecting error checks leads to hard-to-debug issues.

### 4.2 Excessive `__syncthreads()`
Overuse of `__syncthreads()` can severely limit occupancy and performance. Only synchronize when absolutely necessary.

### 4.3 Forgetting `cuda

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

Everything for AI and ML →

From the same set69

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor