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
- 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]>
827 lines
28 KiB
Rust
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,
|
|
¶m_bytes,
|
|
)?;
|
|
}
|
|
|
|
#[cfg(not(feature = "cuda"))]
|
|
{
|
|
let _ = (binary_handle, ¶m_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, ¶ms)
|
|
.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(¶ms).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, ¶ms)
|
|
.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, ¶ms);
|
|
|
|
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");
|
|
}
|
|
}
|
|
}
|