Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,472 @@
//! Extended Kernel Profiler
//!
//! Provides advanced kernel profiling capabilities for RustyTorch.
use parking_lot::RwLock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{Level, debug, info, instrument, span};
use crate::error::{IntegrationError, Result};
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
use gpu_profiler::metal::{MetalProfiler, ProfilingReport, ProfilingSession};
/// Report format for profiling results
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
/// Plain text format
Text,
/// JSON format
Json,
/// CSV format
Csv,
/// HTML format with visualizations
Html,
}
/// Result of a profiling operation
#[derive(Debug, Clone)]
pub struct ProfilingResult {
/// Name of the profiled operation
pub name: String,
/// Execution duration
pub duration: Duration,
/// GPU time (may differ from wall time)
pub gpu_time: Duration,
/// Memory bandwidth achieved (GB/s)
pub bandwidth_gbps: f64,
/// Compute throughput (GFLOPS)
pub gflops: f64,
/// Warp/SIMD occupancy (0.0 - 1.0)
pub occupancy: f64,
/// Memory used (bytes)
pub memory_used: u64,
/// Cache hit rate (0.0 - 1.0)
pub cache_hit_rate: f64,
/// Additional metrics
pub extra_metrics: std::collections::HashMap<String, f64>,
}
impl ProfilingResult {
/// Get warp occupancy as percentage
pub fn warp_occupancy(&self) -> f64 {
self.occupancy
}
/// Get achieved bandwidth in GB/s
pub fn achieved_bandwidth_gbps(&self) -> f64 {
self.bandwidth_gbps
}
/// Check if the kernel achieved good occupancy (>75%)
pub fn is_efficient(&self) -> bool {
self.occupancy > 0.75 && self.cache_hit_rate > 0.8
}
/// Format as string for display
pub fn summary(&self) -> String {
format!(
"{}: {:.2}ms, {:.1}% occupancy, {:.1} GB/s, {:.1} GFLOPS",
self.name,
self.duration.as_secs_f64() * 1000.0,
self.occupancy * 100.0,
self.bandwidth_gbps,
self.gflops
)
}
}
impl Default for ProfilingResult {
fn default() -> Self {
Self {
name: String::new(),
duration: Duration::ZERO,
gpu_time: Duration::ZERO,
bandwidth_gbps: 0.0,
gflops: 0.0,
occupancy: 0.0,
memory_used: 0,
cache_hit_rate: 0.0,
extra_metrics: std::collections::HashMap::new(),
}
}
}
/// Extended kernel profiler for RustyTorch
///
/// Provides detailed profiling of GPU kernels with warp analysis,
/// memory bandwidth tracking, and performance recommendations.
pub struct ExtendedKernelProfiler {
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
metal_profiler: Option<MetalProfiler>,
/// Collected profiling results
results: Arc<RwLock<Vec<ProfilingResult>>>,
/// Whether profiling is enabled
enabled: bool,
/// Minimum duration to record (filters out tiny operations)
min_duration: Duration,
}
impl ExtendedKernelProfiler {
/// Create a new profiler
pub fn new() -> Result<Self> {
info!("Creating ExtendedKernelProfiler");
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
let metal_profiler = {
match MetalProfiler::new() {
Ok(p) => Some(p),
Err(e) => {
debug!("Metal profiler unavailable: {}", e);
None
}
}
};
Ok(Self {
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
metal_profiler,
results: Arc::new(RwLock::new(Vec::new())),
enabled: true,
min_duration: Duration::from_micros(10),
})
}
/// Enable or disable profiling
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
/// Check if profiling is enabled
pub fn is_enabled(&self) -> bool {
self.enabled
}
/// Set minimum duration for recording
pub fn set_min_duration(&mut self, duration: Duration) {
self.min_duration = duration;
}
/// Profile a kernel launch
///
/// # Example
/// ```ignore
/// let profiler = ExtendedKernelProfiler::new()?;
/// let result = profiler.profile_launch("matmul", || {
/// tensor_a.matmul(&tensor_b)
/// });
/// println!("Occupancy: {:.1}%", result.warp_occupancy() * 100.0);
/// ```
#[instrument(skip(self, f), fields(name = %name))]
pub fn profile_launch<F, R>(&self, name: &str, f: F) -> ProfilingResult
where
F: FnOnce() -> R,
{
if !self.enabled {
f();
return ProfilingResult {
name: name.to_string(),
..Default::default()
};
}
let _span = span!(Level::TRACE, "kernel", name = name).entered();
// Start timing
let start = Instant::now();
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
let session: Option<ProfilingSession> = self
.metal_profiler
.as_ref()
.and_then(|p| p.begin_session(name).ok());
// Execute the operation
let _result = f();
// Stop timing
let duration = start.elapsed();
// End profiling session and get report
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
let report: Option<ProfilingReport> = session.and_then(|s| {
self.metal_profiler
.as_ref()
.and_then(|p| p.end_session(s).ok())
});
// Build result
let mut profiling_result = ProfilingResult {
name: name.to_string(),
duration,
gpu_time: duration, // Will be updated with actual GPU time if available
..Default::default()
};
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
if let Some(metrics) = report {
profiling_result.gpu_time = metrics.duration;
profiling_result.occupancy = metrics.average_gpu_utilization();
profiling_result.memory_used = metrics.peak_memory_usage() as u64;
// Calculate bandwidth from samples if available
if let Some(sample) = metrics.samples.first() {
let bandwidth_bytes =
sample.counters.memory_read_bandwidth + sample.counters.memory_write_bandwidth;
profiling_result.bandwidth_gbps = bandwidth_bytes as f64 / 1e9;
profiling_result.cache_hit_rate = sample.counters.l1_cache_hit_rate;
}
}
// Record if above threshold
if duration >= self.min_duration {
let mut results = self.results.write();
results.push(profiling_result.clone());
}
profiling_result
}
/// Profile an async operation
#[instrument(skip(self, f), fields(name = %name))]
pub async fn profile_async<F, Fut, R>(&self, name: &str, f: F) -> ProfilingResult
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = R>,
{
if !self.enabled {
f().await;
return ProfilingResult {
name: name.to_string(),
..Default::default()
};
}
let start = Instant::now();
// Execute the async operation
let _result = f().await;
let duration = start.elapsed();
let profiling_result = ProfilingResult {
name: name.to_string(),
duration,
gpu_time: duration,
..Default::default()
};
if duration >= self.min_duration {
let mut results = self.results.write();
results.push(profiling_result.clone());
}
profiling_result
}
/// Get all collected results
pub fn results(&self) -> Vec<ProfilingResult> {
self.results.read().clone()
}
/// Clear collected results
pub fn clear(&self) {
self.results.write().clear();
}
/// Export profiling report
pub fn export_report(&self, format: ReportFormat) -> String {
let results = self.results.read();
match format {
ReportFormat::Text => self.format_text(&results),
ReportFormat::Json => self.format_json(&results),
ReportFormat::Csv => self.format_csv(&results),
ReportFormat::Html => self.format_html(&results),
}
}
fn format_text(&self, results: &[ProfilingResult]) -> String {
let mut output = String::new();
output.push_str("=== Kernel Profiling Report ===\n\n");
for result in results {
output.push_str(&format!(
"Kernel: {}\n Duration: {:.3}ms\n GPU Time: {:.3}ms\n Occupancy: {:.1}%\n Bandwidth: {:.1} GB/s\n GFLOPS: {:.1}\n Memory: {} bytes\n\n",
result.name,
result.duration.as_secs_f64() * 1000.0,
result.gpu_time.as_secs_f64() * 1000.0,
result.occupancy * 100.0,
result.bandwidth_gbps,
result.gflops,
result.memory_used,
));
}
// Summary statistics
if !results.is_empty() {
let total_time: f64 = results.iter().map(|r| r.duration.as_secs_f64()).sum();
let avg_occupancy: f64 =
results.iter().map(|r| r.occupancy).sum::<f64>() / results.len() as f64;
output.push_str(&format!(
"=== Summary ===\nTotal Kernels: {}\nTotal Time: {:.3}ms\nAverage Occupancy: {:.1}%\n",
results.len(),
total_time * 1000.0,
avg_occupancy * 100.0,
));
}
output
}
fn format_json(&self, results: &[ProfilingResult]) -> String {
let entries: Vec<_> = results
.iter()
.map(|r| {
serde_json::json!({
"name": r.name,
"duration_ms": r.duration.as_secs_f64() * 1000.0,
"gpu_time_ms": r.gpu_time.as_secs_f64() * 1000.0,
"occupancy": r.occupancy,
"bandwidth_gbps": r.bandwidth_gbps,
"gflops": r.gflops,
"memory_used": r.memory_used,
})
})
.collect();
serde_json::to_string_pretty(&entries).unwrap_or_default()
}
fn format_csv(&self, results: &[ProfilingResult]) -> String {
let mut output = String::from(
"name,duration_ms,gpu_time_ms,occupancy,bandwidth_gbps,gflops,memory_used\n",
);
for r in results {
output.push_str(&format!(
"{},{:.3},{:.3},{:.4},{:.2},{:.2},{}\n",
r.name,
r.duration.as_secs_f64() * 1000.0,
r.gpu_time.as_secs_f64() * 1000.0,
r.occupancy,
r.bandwidth_gbps,
r.gflops,
r.memory_used,
));
}
output
}
fn format_html(&self, results: &[ProfilingResult]) -> String {
let mut output = String::from(
r#"<!DOCTYPE html>
<html><head><title>Kernel Profiling Report</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.good { color: green; }
.bad { color: red; }
</style>
</head><body>
<h1>Kernel Profiling Report</h1>
<table>
<tr><th>Kernel</th><th>Duration (ms)</th><th>Occupancy (%)</th><th>Bandwidth (GB/s)</th><th>GFLOPS</th></tr>
"#,
);
for r in results {
let occupancy_class = if r.occupancy > 0.75 { "good" } else { "bad" };
output.push_str(&format!(
"<tr><td>{}</td><td>{:.3}</td><td class=\"{}\">{:.1}</td><td>{:.1}</td><td>{:.1}</td></tr>\n",
r.name,
r.duration.as_secs_f64() * 1000.0,
occupancy_class,
r.occupancy * 100.0,
r.bandwidth_gbps,
r.gflops,
));
}
output.push_str("</table></body></html>");
output
}
}
impl Default for ExtendedKernelProfiler {
fn default() -> Self {
Self::new().unwrap_or_else(|_| Self {
#[cfg(all(target_os = "macos", feature = "profiling-metal"))]
metal_profiler: None,
results: Arc::new(RwLock::new(Vec::new())),
enabled: false,
min_duration: Duration::from_micros(10),
})
}
}
impl std::fmt::Debug for ExtendedKernelProfiler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExtendedKernelProfiler")
.field("enabled", &self.enabled)
.field("results_count", &self.results.read().len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profiler_creation() {
let profiler = ExtendedKernelProfiler::new();
assert!(profiler.is_ok() || cfg!(not(feature = "profiling-metal")));
}
#[test]
fn test_profiling_result_default() {
let result = ProfilingResult::default();
assert_eq!(result.name, "");
assert_eq!(result.occupancy, 0.0);
}
#[test]
fn test_profiling_result_summary() {
let result = ProfilingResult {
name: "test_kernel".to_string(),
duration: Duration::from_millis(10),
occupancy: 0.85,
bandwidth_gbps: 100.0,
gflops: 1000.0,
..Default::default()
};
let summary = result.summary();
assert!(summary.contains("test_kernel"));
assert!(summary.contains("85.0%"));
}
#[test]
fn test_report_formats() {
let profiler = ExtendedKernelProfiler::default();
// Test each format
let text = profiler.export_report(ReportFormat::Text);
assert!(text.contains("Kernel Profiling Report"));
let csv = profiler.export_report(ReportFormat::Csv);
assert!(csv.contains("name,duration_ms"));
let html = profiler.export_report(ReportFormat::Html);
assert!(html.contains("<!DOCTYPE html>"));
}
}