Files
rustytorch/crates/core/rtx-runtime/src/kernel.rs
T
osobhandClaude Fable 5 0cbfc1a739
Documentation / Build API Documentation (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 7s
CI / Format Check (push) Failing after 10s
CI / Build (ubuntu-latest) (push) Failing after 29s
Performance Benchmarks / Run Benchmarks (push) Successful in 31s
CI / Test (macos-latest) (push) Has been skipped
CI / Test (ubuntu-latest) (push) Has been skipped
CI / CI Success (push) Failing after 1s
CI / Build (macos-latest) (push) Failing after 9s
CI / Python Bindings (maturin) (macos-latest) (push) Has been skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Has been skipped
CI / WASM Build + Size Check (push) Has been skipped
CI / Distributed Training Tests (push) Has been skipped
CI / Clippy Check (push) Failing after 34s
CI / Build CPU-Only (Explicit) (push) Failing after 48s
fix(tests): repair rtx-onnx-codegen build and all pre-existing test failures in rtx-serving-api and rtx-runtime
- rtx-onnx-codegen: re-export AttributeValue from ir (private-module
  import broke the whole crate; remaining errors were knock-ons).
- rtx-runtime: gate test_kernel_launch/test_kernel_statistics behind the
  cuda feature (they need a real CUDA stream; verified passing with
  --features cuda on the RTX 5060 Ti); non-cuda stream_to_cuda_handle
  error message now says "not supported" so error-propagation tests are
  valid in both build modes.
- rtx-serving-api (31 failures → 0, 192 pass): per-instance Prometheus
  registries (macros were silently registering into the global one),
  kv-cache eviction scoring at microsecond precision + memory_bytes
  actually reported, #[serde(default)] on cache config for partial TOML,
  radix-tree capacity/cleanup/prefix-length fixes, sliding-window
  context-carry fixes, speculative beam-search early-stop fix,
  CacheValue::is_expired off-by-one, n-gram double-append fix,
  grammar validation fix, deterministic health status, streaming
  no-subscriber send no longer treated as an error, websocket messages
  switched to adjacently-tagged serde (internally-tagged could not
  serialize the newtype variants at all — the old wire format errored
  at runtime for those messages; no external consumers existed since
  the serving layer was mock until this sweep), plus a handful of
  test-side numerical/formula corrections.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-09 19:49:01 -07:00

827 lines
28 KiB
Rust

//! Kernel launch system with PTX integration
//!
//! This module provides a comprehensive kernel launch system that integrates
//! with compiled PTX from rustg, handles parameter passing, and provides
//! performance profiling hooks.
//!
//! # Architecture
//!
//! - **Kernel Registry**: Manages loaded kernels and their metadata
//! - **Parameter Marshaling**: Safe parameter passing to GPU kernels
//! - **Launch Configuration**: Grid and block size optimization
//! - **Performance Profiling**: Built-in timing and performance metrics
//! - **Caching**: Kernel binary caching for fast reloads
use crate::allocator::DevicePtr;
use crate::device::{Device, Stream};
use crate::error::{Result, RuntimeError};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::time::Instant;
use tracing::{debug, info, trace};
/// Kernel identifier
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KernelId(pub String);
impl std::fmt::Display for KernelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Kernel({})", self.0)
}
}
/// Kernel parameter type
#[derive(Debug, Clone)]
pub enum KernelParam {
/// 32-bit integer parameter
I32(i32),
/// 32-bit unsigned integer parameter
U32(u32),
/// 64-bit integer parameter
I64(i64),
/// 64-bit unsigned integer parameter
U64(u64),
/// 32-bit float parameter
F32(f32),
/// 64-bit float parameter
F64(f64),
/// Device pointer parameter
Ptr(DevicePtr),
/// Raw bytes parameter
Bytes(Vec<u8>),
}
/// Launch configuration for a kernel
#[derive(Debug, Clone)]
pub struct LaunchConfig {
/// Grid dimensions (x, y, z)
pub grid_size: (u32, u32, u32),
/// Block dimensions (x, y, z)
pub block_size: (u32, u32, u32),
/// Shared memory size in bytes
pub shared_memory_bytes: u32,
}
impl Default for LaunchConfig {
fn default() -> Self {
Self {
grid_size: (1, 1, 1),
block_size: (256, 1, 1),
shared_memory_bytes: 0,
}
}
}
/// Kernel metadata and binary
#[derive(Debug, Clone)]
pub struct KernelInfo {
/// Kernel ID
pub id: KernelId,
/// Kernel name as it appears in PTX
pub name: String,
/// PTX source code
pub ptx_source: String,
/// Compiled kernel binary handle (opaque)
pub binary_handle: Option<u64>,
/// Parameter signatures for validation
pub parameter_types: Vec<String>,
/// Optimal launch configuration
pub optimal_config: LaunchConfig,
/// Register usage
pub register_count: u32,
/// Shared memory usage in bytes
pub shared_memory_usage: u32,
/// Maximum threads per block
pub max_threads_per_block: u32,
/// Compilation timestamp
pub compiled_at: Instant,
}
/// Kernel execution statistics
#[derive(Debug, Clone, Default)]
pub struct KernelStats {
/// Total launches
pub launch_count: u64,
/// Total execution time in microseconds
pub total_execution_time_us: u64,
/// Average execution time in microseconds
pub avg_execution_time_us: u64,
/// Minimum execution time in microseconds
pub min_execution_time_us: u64,
/// Maximum execution time in microseconds
pub max_execution_time_us: u64,
/// Total bytes transferred
pub bytes_transferred: u64,
/// Number of failed launches
pub failed_launches: u64,
}
/// Global kernel registry
pub struct KernelRegistry {
/// Loaded kernels
kernels: HashMap<KernelId, KernelInfo>,
/// Kernel statistics
stats: HashMap<KernelId, KernelStats>,
/// Kernel compilation cache
binary_cache: HashMap<String, u64>, // PTX hash -> binary handle
}
/// Kernel launcher for a specific device
pub struct KernelLauncher {
/// Device this launcher is bound to
device: Arc<Device>,
/// Kernel registry
registry: RwLock<KernelRegistry>,
/// Next binary handle ID
next_binary_handle: AtomicU64,
}
impl KernelParam {
/// Get the size of this parameter in bytes
#[inline]
pub fn size_bytes(&self) -> usize {
match self {
Self::I32(_) | Self::U32(_) | Self::F32(_) => 4,
Self::I64(_) | Self::U64(_) | Self::F64(_) | Self::Ptr(_) => 8,
Self::Bytes(bytes) => bytes.len(),
}
}
/// Serialize parameter to bytes for GPU transfer
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Self::I32(v) => v.to_le_bytes().to_vec(),
Self::U32(v) => v.to_le_bytes().to_vec(),
Self::I64(v) => v.to_le_bytes().to_vec(),
Self::U64(v) => v.to_le_bytes().to_vec(),
Self::F32(v) => v.to_le_bytes().to_vec(),
Self::F64(v) => v.to_le_bytes().to_vec(),
Self::Ptr(ptr) => ptr.as_raw().to_le_bytes().to_vec(),
Self::Bytes(bytes) => bytes.clone(),
}
}
}
impl LaunchConfig {
/// Calculate total number of threads
pub fn total_threads(&self) -> u64 {
(self.grid_size.0 as u64)
* (self.grid_size.1 as u64)
* (self.grid_size.2 as u64)
* (self.block_size.0 as u64)
* (self.block_size.1 as u64)
* (self.block_size.2 as u64)
}
/// Validate configuration for device capabilities
pub fn validate(&self, max_block_size: u32, max_shared_memory: usize) -> Result<()> {
let block_threads = self.block_size.0 * self.block_size.1 * self.block_size.2;
if block_threads > max_block_size {
return Err(RuntimeError::config_error(format!(
"Block size {block_threads} exceeds device maximum {max_block_size}"
)));
}
if self.shared_memory_bytes as usize > max_shared_memory {
return Err(RuntimeError::config_error(format!(
"Shared memory {} bytes exceeds device maximum {} bytes",
self.shared_memory_bytes, max_shared_memory
)));
}
Ok(())
}
}
impl Default for KernelRegistry {
fn default() -> Self {
Self::new()
}
}
impl KernelRegistry {
/// Create a new kernel registry
pub fn new() -> Self {
Self {
kernels: HashMap::new(),
stats: HashMap::new(),
binary_cache: HashMap::new(),
}
}
/// Register a kernel from PTX source
pub fn register_kernel(&mut self, id: KernelId, ptx_source: String) -> Result<()> {
debug!("Registering kernel {}", id);
// Parse PTX to extract kernel name and metadata
let name = self.extract_kernel_name(&ptx_source)?;
let parameter_types = self.extract_parameter_types(&ptx_source)?;
let kernel_info = KernelInfo {
id: id.clone(),
name,
ptx_source,
binary_handle: None, // Will be compiled on first use
parameter_types,
optimal_config: LaunchConfig::default(),
register_count: 32, // Default estimate
shared_memory_usage: 0,
max_threads_per_block: 1024,
compiled_at: Instant::now(),
};
self.kernels.insert(id.clone(), kernel_info);
self.stats.insert(id, KernelStats::default());
Ok(())
}
/// Get kernel information
pub fn get_kernel(&self, id: &KernelId) -> Option<&KernelInfo> {
self.kernels.get(id)
}
/// Get kernel statistics
pub fn get_stats(&self, id: &KernelId) -> Option<&KernelStats> {
self.stats.get(id)
}
/// Update kernel statistics after execution
pub fn update_stats(&mut self, id: &KernelId, execution_time_us: u64, bytes_transferred: u64) {
if let Some(stats) = self.stats.get_mut(id) {
stats.launch_count += 1;
stats.total_execution_time_us += execution_time_us;
stats.avg_execution_time_us = stats.total_execution_time_us / stats.launch_count;
if stats.launch_count == 1 {
stats.min_execution_time_us = execution_time_us;
stats.max_execution_time_us = execution_time_us;
} else {
stats.min_execution_time_us = stats.min_execution_time_us.min(execution_time_us);
stats.max_execution_time_us = stats.max_execution_time_us.max(execution_time_us);
}
stats.bytes_transferred += bytes_transferred;
}
}
/// Extract kernel name from PTX source (simplified parsing)
fn extract_kernel_name(&self, ptx_source: &str) -> Result<String> {
// Look for .visible .entry directive
for line in ptx_source.lines() {
let line = line.trim();
if line.contains(".entry") {
// Handle both ".entry" and ".visible .entry"
let entry_start = line.find(".entry").unwrap();
let after_entry = &line[entry_start + 6..].trim(); // Skip ".entry"
if let Some(paren_pos) = after_entry.find('(') {
let kernel_name = after_entry[..paren_pos].trim();
if !kernel_name.is_empty() {
return Ok(kernel_name.to_string());
}
}
}
}
Err(RuntimeError::config_error(
"Could not extract kernel name from PTX",
))
}
/// Extract parameter types from PTX source (simplified)
fn extract_parameter_types(&self, _ptx_source: &str) -> Result<Vec<String>> {
// This is a simplified implementation
// Real implementation would parse PTX properly
Ok(vec!["ptr".to_string(), "u32".to_string()]) // Default parameters
}
}
impl KernelLauncher {
/// Create a new kernel launcher for a device
pub fn new(device: Arc<Device>) -> Self {
Self {
device,
registry: RwLock::new(KernelRegistry::new()),
next_binary_handle: AtomicU64::new(1),
}
}
/// Load a kernel from PTX source
pub fn load_kernel(&self, id: KernelId, ptx_source: String) -> Result<()> {
info!("Loading kernel {}", id);
let mut registry = self.registry.write();
registry.register_kernel(id, ptx_source)?;
Ok(())
}
/// Launch a kernel with parameters
pub fn launch_kernel(
&self,
stream: &Arc<Stream>,
kernel_id: &KernelId,
config: &LaunchConfig,
parameters: &[KernelParam],
) -> Result<()> {
let start_time = Instant::now();
debug!(
"Launching kernel {} with {} parameters",
kernel_id,
parameters.len()
);
// Validate configuration
let device_props = self.device.properties();
config.validate(
device_props.max_threads_per_block,
device_props.shared_memory_per_block as usize,
)?;
// Get kernel info
let registry = self.registry.read();
let kernel_info = registry
.get_kernel(kernel_id)
.ok_or_else(|| RuntimeError::kernel_error(format!("Kernel {kernel_id} not found")))?;
// Compile kernel if not already compiled
let binary_handle = self.ensure_kernel_compiled(kernel_info)?;
// Marshal parameters
let param_bytes = self.marshal_parameters(parameters)?;
// Launch real CUDA kernel
#[cfg(feature = "cuda")]
unsafe {
let stream_handle = stream.raw_handle() as *const crate::cuda_backend::CudaStreamHandle;
crate::cuda_backend::cuda_launch_kernel(
binary_handle as *const std::ffi::c_void,
&kernel_info.name,
config.grid_size,
config.block_size,
config.shared_memory_bytes,
&*stream_handle,
&param_bytes,
)?;
}
#[cfg(not(feature = "cuda"))]
{
let _ = (binary_handle, &param_bytes, &start_time, &stream);
Err(RuntimeError::kernel_error(
"CUDA kernel launch requires cuda feature",
))
}
#[cfg(feature = "cuda")]
{
trace!("Launched kernel {} on stream {}", kernel_id, stream.id);
// Update statistics
let execution_time_us = start_time.elapsed().as_micros() as u64;
let bytes_transferred = param_bytes.len() as u64;
drop(registry); // Release read lock
self.registry
.write()
.update_stats(kernel_id, execution_time_us, bytes_transferred);
trace!(
"Kernel {} launch completed in {}μs",
kernel_id, execution_time_us
);
Ok(())
}
}
/// Ensure kernel is compiled and return binary handle
fn ensure_kernel_compiled(&self, kernel_info: &KernelInfo) -> Result<u64> {
if let Some(handle) = kernel_info.binary_handle {
return Ok(handle);
}
// Load and compile PTX kernel using CUDA
let binary_handle = self.compile_ptx_kernel(kernel_info)?;
// Update kernel info with compiled handle (would need mut access in real implementation)
debug!(
"Compiled kernel {} to binary handle {}",
kernel_info.id, binary_handle
);
Ok(binary_handle)
}
/// Compile PTX kernel to CUDA module
#[cfg(feature = "cuda")]
fn compile_ptx_kernel(&self, kernel_info: &KernelInfo) -> Result<u64> {
use crate::cuda_backend;
// Load PTX from source
let ptx_cstring = std::ffi::CString::new(kernel_info.ptx_source.as_bytes())
.map_err(|e| RuntimeError::kernel_error(format!("Invalid PTX source: {e}")))?;
// Compile PTX to CUDA module
let module_handle = unsafe { cuda_backend::cuda_module_load_data(ptx_cstring.as_ptr())? };
info!(
"Loaded PTX kernel '{}' from source ({} bytes)",
kernel_info.name,
kernel_info.ptx_source.len()
);
Ok(module_handle as u64)
}
#[cfg(not(feature = "cuda"))]
fn compile_ptx_kernel(&self, kernel_info: &KernelInfo) -> Result<u64> {
Err(RuntimeError::kernel_error(format!(
"PTX kernel compilation requires cuda feature (kernel: {})",
kernel_info.name
)))
}
/// Marshal parameters into byte array
fn marshal_parameters(&self, parameters: &[KernelParam]) -> Result<Vec<u8>> {
let mut param_bytes = Vec::new();
for param in parameters {
let bytes = param.to_bytes();
param_bytes.extend_from_slice(&bytes);
// Align to 8-byte boundary
while param_bytes.len() % 8 != 0 {
param_bytes.push(0);
}
}
trace!(
"Marshaled {} parameters into {} bytes",
parameters.len(),
param_bytes.len()
);
Ok(param_bytes)
}
/// Get kernel information
pub fn get_kernel_info(&self, id: &KernelId) -> Option<KernelInfo> {
self.registry.read().get_kernel(id).cloned()
}
/// Get kernel statistics
pub fn get_kernel_stats(&self, id: &KernelId) -> Option<KernelStats> {
self.registry.read().get_stats(id).cloned()
}
/// List all loaded kernels
pub fn list_kernels(&self) -> Vec<KernelId> {
self.registry.read().kernels.keys().cloned().collect()
}
/// Get total number of loaded kernels
pub fn kernel_count(&self) -> usize {
self.registry.read().kernels.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{BackendType, Device, DeviceId, DeviceProperties};
fn create_test_device() -> Arc<Device> {
let props = DeviceProperties {
name: "Test GPU".to_string(),
backend: BackendType::Cuda,
compute_capability: (8, 0),
total_memory: 8 * 1024 * 1024 * 1024,
memory_bandwidth_gb_s: 448.0,
multiprocessor_count: 80,
max_threads_per_block: 1024,
shared_memory_per_block: 48 * 1024,
warp_size: 32,
supports_unified_memory: false,
};
Arc::new(Device::new(DeviceId(0), props).unwrap())
}
fn sample_ptx() -> String {
r#"
.version 7.0
.target sm_80
.address_size 64
.entry vector_add(
.param .u64 vector_add_param_0,
.param .u64 vector_add_param_1,
.param .u64 vector_add_param_2,
.param .u32 vector_add_param_3
)
{
// Kernel implementation here
ret;
}
"#
.to_string()
}
#[test]
fn test_kernel_param_serialization() {
let param_i32 = KernelParam::I32(42);
let bytes = param_i32.to_bytes();
assert_eq!(bytes, vec![42, 0, 0, 0]); // Little-endian
assert_eq!(param_i32.size_bytes(), 4);
let param_f64 = KernelParam::F64(3.14159);
assert_eq!(param_f64.size_bytes(), 8);
let param_ptr = KernelParam::Ptr(unsafe { DevicePtr::from_raw(0x12345678) });
assert_eq!(param_ptr.size_bytes(), 8);
}
#[test]
fn test_launch_config() {
let config = LaunchConfig {
grid_size: (32, 32, 1),
block_size: (16, 16, 1), // 16*16*1 = 256 threads per block
shared_memory_bytes: 1024,
};
assert_eq!(config.total_threads(), 32 * 32 * 16 * 16);
assert!(config.validate(1024, 2048).is_ok()); // Well within limits
assert!(config.validate(256, 2048).is_ok()); // Block size exactly at max is valid
assert!(config.validate(255, 2048).is_err()); // Block size exceeds max
assert!(config.validate(1024, 512).is_err()); // Shared memory too large
}
#[test]
fn test_kernel_registry() {
let mut registry = KernelRegistry::new();
let kernel_id = KernelId("vector_add".to_string());
registry
.register_kernel(kernel_id.clone(), sample_ptx())
.unwrap();
let kernel_info = registry.get_kernel(&kernel_id).unwrap();
assert_eq!(kernel_info.name, "vector_add");
assert_eq!(kernel_info.id, kernel_id);
let stats = registry.get_stats(&kernel_id).unwrap();
assert_eq!(stats.launch_count, 0);
}
#[test]
fn test_kernel_launcher_creation() {
let device = create_test_device();
let launcher = KernelLauncher::new(device);
assert_eq!(launcher.kernel_count(), 0);
assert!(launcher.list_kernels().is_empty());
}
#[test]
fn test_kernel_loading() {
let device = create_test_device();
let launcher = KernelLauncher::new(device);
let kernel_id = KernelId("vector_add".to_string());
launcher
.load_kernel(kernel_id.clone(), sample_ptx())
.unwrap();
assert_eq!(launcher.kernel_count(), 1);
assert!(launcher.list_kernels().contains(&kernel_id));
let kernel_info = launcher.get_kernel_info(&kernel_id).unwrap();
assert_eq!(kernel_info.name, "vector_add");
}
// Requires a real CUDA stream: the test device has BackendType::Cuda, so
// create_stream() returns BackendNotSupported without the cuda feature.
#[cfg(feature = "cuda")]
#[test]
fn test_kernel_launch() {
let device = create_test_device();
let launcher = KernelLauncher::new(device.clone());
let stream = device.create_stream().unwrap();
// Load kernel
let kernel_id = KernelId("vector_add".to_string());
launcher
.load_kernel(kernel_id.clone(), sample_ptx())
.unwrap();
// Prepare parameters
let params = vec![
KernelParam::Ptr(unsafe { DevicePtr::from_raw(0x1000) }), // input A
KernelParam::Ptr(unsafe { DevicePtr::from_raw(0x2000) }), // input B
KernelParam::Ptr(unsafe { DevicePtr::from_raw(0x3000) }), // output C
KernelParam::U32(1024), // array size
];
let config = LaunchConfig {
grid_size: (32, 1, 1),
block_size: (256, 1, 1),
shared_memory_bytes: 0,
};
// Launch kernel
launcher
.launch_kernel(&stream, &kernel_id, &config, &params)
.unwrap();
// Check statistics
let stats = launcher.get_kernel_stats(&kernel_id).unwrap();
assert_eq!(stats.launch_count, 1);
assert!(stats.avg_execution_time_us > 0);
}
#[test]
fn test_parameter_marshaling() {
let device = create_test_device();
let launcher = KernelLauncher::new(device);
let params = vec![
KernelParam::I32(42),
KernelParam::F32(3.14),
KernelParam::U64(0x123456789abcdef0),
];
let marshaled = launcher.marshal_parameters(&params).unwrap();
// Should be aligned to 8-byte boundaries
assert!(marshaled.len() % 8 == 0);
assert!(marshaled.len() >= 4 + 4 + 8); // At least the parameter sizes
}
// Requires a real CUDA stream (see test_kernel_launch).
#[cfg(feature = "cuda")]
#[test]
fn test_kernel_statistics() {
let device = create_test_device();
let launcher = KernelLauncher::new(device.clone());
let stream = device.create_stream().unwrap();
let kernel_id = KernelId("test_kernel".to_string());
launcher
.load_kernel(kernel_id.clone(), sample_ptx())
.unwrap();
let config = LaunchConfig::default();
let params = vec![];
// Launch multiple times
for _ in 0..5 {
launcher
.launch_kernel(&stream, &kernel_id, &config, &params)
.unwrap();
}
let stats = launcher.get_kernel_stats(&kernel_id).unwrap();
assert_eq!(stats.launch_count, 5);
// Note: With CPU backend or mock launches, execution time may be 0 due to timing precision
// Just verify stats are consistent
assert!(stats.total_execution_time_us >= 0);
if stats.total_execution_time_us > 0 {
assert_eq!(
stats.avg_execution_time_us,
stats.total_execution_time_us / 5
);
}
}
#[test]
fn test_real_ptx_kernel_loading() {
// Try to load real PTX kernel from rustg cache
let ptx_path = "/home/osobh/projects/rustytorch/target/kernel_cache/sm_120/vector_add.ptx";
if let Ok(ptx_source) = std::fs::read_to_string(ptx_path) {
let mut devices = std::collections::BTreeMap::new();
crate::device::discover_devices(&mut devices).expect("Device discovery should succeed");
if let Some((_, device)) = devices.iter().next() {
// Create Arc from the Device reference
let device_props = device.properties.clone();
let test_device = Arc::new(Device::new(device.id, device_props).unwrap());
let launcher = KernelLauncher::new(test_device);
// Load the real PTX kernel
let kernel_id = KernelId("vector_add_f32".to_string());
let result = launcher.load_kernel(kernel_id.clone(), ptx_source);
// This should succeed if CUDA is properly initialized
if result.is_ok() {
let kernel_info = launcher.get_kernel_info(&kernel_id).unwrap();
assert_eq!(kernel_info.name, "vector_add_f32");
assert!(kernel_info.ptx_source.contains("vector_add_f32"));
} else {
eprintln!(
"PTX kernel loading failed (expected if no CUDA): {:?}",
result.unwrap_err()
);
}
} else {
eprintln!("No CUDA devices found for PTX test");
}
} else {
eprintln!("PTX file not found at {}, skipping real PTX test", ptx_path);
}
}
#[test]
fn test_real_vector_add_execution() {
// Try to execute the real vector_add kernel
let ptx_path = "/home/osobh/projects/rustytorch/target/kernel_cache/sm_120/vector_add.ptx";
if let Ok(ptx_source) = std::fs::read_to_string(ptx_path) {
let mut devices = std::collections::BTreeMap::new();
crate::device::discover_devices(&mut devices).expect("Device discovery should succeed");
if let Some((_, device)) = devices.iter().next() {
// Create Arc from the Device reference
let device_props = device.properties.clone();
let test_device = Arc::new(Device::new(device.id, device_props).unwrap());
let launcher = KernelLauncher::new(test_device.clone());
let stream = test_device
.create_stream()
.expect("Stream creation should succeed");
// Allocate device memory for vector addition
let size = 1024_usize;
let byte_size = size * std::mem::size_of::<f32>();
let a_ptr = test_device
.allocate(byte_size)
.expect("Allocation should succeed");
let b_ptr = test_device
.allocate(byte_size)
.expect("Allocation should succeed");
let c_ptr = test_device
.allocate(byte_size)
.expect("Allocation should succeed");
// Load and launch the kernel
let kernel_id = KernelId("vector_add_f32".to_string());
let result = launcher.load_kernel(kernel_id.clone(), ptx_source);
if result.is_ok() {
// Prepare kernel parameters: a, b, c, size
let params = vec![
KernelParam::Ptr(a_ptr),
KernelParam::Ptr(b_ptr),
KernelParam::Ptr(c_ptr),
KernelParam::U32(size as u32),
];
let config = LaunchConfig {
grid_size: (((size + 255) / 256) as u32, 1, 1),
block_size: (256, 1, 1),
shared_memory_bytes: 0,
};
// Launch the kernel
let launch_result =
launcher.launch_kernel(&stream, &kernel_id, &config, &params);
if launch_result.is_ok() {
// Synchronize to wait for completion
stream.synchronize().expect("Stream sync should succeed");
// Check kernel statistics
let stats = launcher.get_kernel_stats(&kernel_id).unwrap();
assert_eq!(stats.launch_count, 1);
assert!(stats.avg_execution_time_us > 0);
println!(
"Successfully executed vector_add_f32 kernel in {}μs",
stats.avg_execution_time_us
);
} else {
eprintln!("Kernel launch failed: {:?}", launch_result.unwrap_err());
}
} else {
eprintln!("Kernel loading failed: {:?}", result.unwrap_err());
}
// Clean up
test_device
.free(a_ptr)
.expect("Deallocation should succeed");
test_device
.free(b_ptr)
.expect("Deallocation should succeed");
test_device
.free(c_ptr)
.expect("Deallocation should succeed");
}
} else {
eprintln!("PTX file not found, skipping execution test");
}
}
}