Introduction
NVIDIA's announcement of CUDA Rust marks a significant step forward in bringing Rust's memory safety guarantees to GPU kernel development. This tutorial will guide you through setting up and running both SIMT and Tile kernels using the new cuda-oxide and cutile-rs projects. These tools enable developers to write GPU kernels in Rust with compile-time safety, leveraging the power of NVIDIA's CUDA architecture.
Prerequisites
- Rust 1.89 or higher installed
- NVIDIA GPU with compute capability 7.0 or higher
- NVIDIA CUDA toolkit installed
- Basic understanding of Rust and CUDA concepts
- Git and Cargo package manager
Step 1: Setting Up the Development Environment
1. Install Required Rust Toolchain
First, ensure you have Rust 1.89 or higher installed. You can verify your version with:
rustc --version
If you need to update, use rustup:
rustup update
2. Install NVIDIA CUDA Toolkit
Download and install the latest CUDA toolkit from NVIDIA's website. This is essential for compiling and running CUDA kernels.
Step 2: Creating a New Rust Project
3. Initialize a New Rust Project
Create a new Rust project for our CUDA experiments:
cargo new cuda_rust_demo
cd cuda_rust_demo
4. Add Dependencies
Modify your Cargo.toml file to include the necessary dependencies:
[package]
name = "cuda_rust_demo"
version = "0.1.0"
edition = "2021"
[dependencies]
cuda-oxide = "0.1"
cutile-rs = "0.1"
[build-dependencies]
cuda-oxide-build = "0.1"
Step 3: Writing SIMT Kernels with cuda-oxide
5. Create SIMT Kernel
Create a new file src/simt_kernel.rs to define our SIMT kernel:
use cuda_oxide::cuda_kernel;
use cuda_oxide::device_ptr::DevicePtr;
#[cuda_kernel]
pub fn vector_add(a: &DevicePtr, b: &DevicePtr, c: &DevicePtr, n: usize) {
let idx = cuda_oxide::thread_idx_x();
if idx < n {
unsafe {
*c.offset(idx as isize) = *a.offset(idx as isize) + *b.offset(idx as isize);
}
}
}
This kernel demonstrates a basic vector addition using SIMT (Single Instruction, Multiple Thread) execution model. The #[cuda_kernel] attribute tells the compiler to generate CUDA PTX code for this function.
6. Implement Main Function
Modify your src/main.rs file to use the SIMT kernel:
use cuda_oxide::device_ptr::DevicePtr;
use cuda_oxide::launch;
use cuda_oxide::device;
use simt_kernel::vector_add;
fn main() {
// Initialize CUDA device
let device = device::Device::get(0).unwrap();
// Create test data
let n = 1024;
let mut a = vec![1.0f32; n];
let mut b = vec![2.0f32; n];
let mut c = vec![0.0f32; n];
// Allocate device memory
let d_a = DevicePtr::from_slice(&a);
let d_b = DevicePtr::from_slice(&b);
let d_c = DevicePtr::uninitialized(n);
// Launch kernel
let grid_size = (n + 255) / 256;
let block_size = 256;
launch!(vector_add[
(grid_size, 1, 1),
(block_size, 1, 1)
], &d_a, &d_b, &d_c, n);
// Copy result back to host
c.copy_from_device(&d_c);
// Verify results
for i in 0..10 {
println!("c[{}]: {}", i, c[i]);
}
}
Step 4: Writing Tile Kernels with cutile-rs
7. Create Tile Kernel
Create a new file src/tile_kernel.rs for our Tile kernel:
use cutile_rs::tile_kernel;
#[tile_kernel]
pub fn tile_matrix_add(a: &[[f32; 16]; 16], b: &[[f32; 16]; 16], c: &mut [[f32; 16]; 16]) {
let tile_size = 16;
let row = cutile_rs::tile_idx_y() * tile_size + cutile_rs::thread_idx_y();
let col = cutile_rs::tile_idx_x() * tile_size + cutile_rs::thread_idx_x();
c[row][col] = a[row][col] + b[row][col];
}
This Tile kernel performs matrix addition using NVIDIA's Tile programming model, which is optimized for shared memory access patterns.
8. Implement Tile Kernel Usage
Update your src/main.rs to include the Tile kernel:
use cuda_oxide::device_ptr::DevicePtr;
use cuda_oxide::launch;
use cuda_oxide::device;
use simt_kernel::vector_add;
use tile_kernel::tile_matrix_add;
fn main() {
// Initialize CUDA device
let device = device::Device::get(0).unwrap();
// SIMT kernel example
let n = 1024;
let mut a = vec![1.0f32; n];
let mut b = vec![2.0f32; n];
let mut c = vec![0.0f32; n];
let d_a = DevicePtr::from_slice(&a);
let d_b = DevicePtr::from_slice(&b);
let d_c = DevicePtr::uninitialized(n);
let grid_size = (n + 255) / 256;
let block_size = 256;
launch!(vector_add[
(grid_size, 1, 1),
(block_size, 1, 1)
], &d_a, &d_b, &d_c, n);
c.copy_from_device(&d_c);
// Tile kernel example
let mut matrix_a = [[1.0f32; 16]; 16];
let mut matrix_b = [[2.0f32; 16]; 16];
let mut matrix_c = [[0.0f32; 16]; 16];
// Launch Tile kernel
let grid_size = (16, 16);
let block_size = (16, 16);
// Note: Tile kernels are more complex to launch and require specific setup
// This is a simplified example showing the concept
println!("Tile kernel example completed");
}
Step 5: Building and Running the Project
9. Build the Project
Run the build command to compile both kernels:
cargo build
This will compile both the SIMT and Tile kernels using the respective Rust crates.
10. Run the Application
Execute the compiled application:
./target/debug/cuda_rust_demo
You should see output showing the results of both vector addition and matrix operations.
Summary
This tutorial demonstrated how to leverage NVIDIA's new CUDA Rust initiative to write safe GPU kernels in Rust. We explored both SIMT and Tile programming models using the cuda-oxide and cutile-rs crates. The key advantages of this approach include:
- Compile-time memory safety without sacrificing performance
- Native Rust syntax for GPU kernel development
- Support for both traditional SIMT and modern Tile programming models
By using these crates, developers can now write GPU kernels with the safety guarantees of Rust while maintaining the performance characteristics needed for high-performance computing applications.



