Batch Windows DLSS GPU inputs and add deferred kernel diagnostics
This commit is contained in:
1 parent
4f3b3a9fe6
commit
973eb5ecd2
15 files changed
+692
-37
No files matched your search
@@ -0,0 +1,282 @@
|
||||
From 26a43f7047ab933a4afb5ebdd26d5ba9e51e58a3 Mon Sep 17 00:00:00 2001
|
||||
From: xdfnx-dev <[email protected]>
|
||||
Date: Fri, 2 Oct 2026 11:50:45 +0300
|
||||
Subject: [PATCH 16/17] Sample HIP kernel events without per-launch completion
|
||||
waits
|
||||
|
||||
---
|
||||
zluda/src/impl/context.rs | 4 +-
|
||||
zluda/src/impl/d4r_profile.rs | 204 ++++++++++++++++++++++++++++------
|
||||
zluda/src/impl/module.rs | 1 +
|
||||
3 files changed, 171 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/zluda/src/impl/context.rs b/zluda/src/impl/context.rs
|
||||
index 9ccb372..374d383 100644
|
||||
--- a/zluda/src/impl/context.rs
|
||||
+++ b/zluda/src/impl/context.rs
|
||||
@@ -165,7 +165,7 @@ pub(crate) fn set_cache_config(config: CUfunc_cache) -> CUresult {
|
||||
}
|
||||
|
||||
pub(crate) fn synchronize() -> hipError_t {
|
||||
- unsafe { hipDeviceSynchronize() }
|
||||
+ unsafe { hipDeviceSynchronize()?; super::d4r_profile::collect_completed() }
|
||||
}
|
||||
|
||||
pub(crate) fn synchronize_v2(ctx: &Context) -> hipError_t {
|
||||
@@ -177,7 +177,7 @@ pub(crate) fn synchronize_v2(ctx: &Context) -> hipError_t {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
- unsafe { hipDeviceSynchronize() }
|
||||
+ unsafe { hipDeviceSynchronize()?; super::d4r_profile::collect_completed() }
|
||||
}
|
||||
|
||||
struct SetDeviceOnDrop(i32);
|
||||
diff --git a/zluda/src/impl/d4r_profile.rs b/zluda/src/impl/d4r_profile.rs
|
||||
index 33ba5f6..1b9d248 100644
|
||||
--- a/zluda/src/impl/d4r_profile.rs
|
||||
+++ b/zluda/src/impl/d4r_profile.rs
|
||||
@@ -1,15 +1,29 @@
|
||||
-//! Optional, deliberately serializing HIP-event diagnostics. Not a production
|
||||
-//! scheduler or a claim about application FPS / measured hardware occupancy.
|
||||
+//! Optional HIP-event diagnostics. Deferred mode never waits for its events,
|
||||
+//! skips the legacy default stream and bounds outstanding events per thread.
|
||||
+//! Timestamp insertion still adds overhead; this is not a production scheduler.
|
||||
use hip_runtime_sys::*;
|
||||
-use std::{ffi::CStr, ptr, time::Instant};
|
||||
+use std::{cell::RefCell, ffi::{CStr, CString}, ptr, sync::OnceLock, time::Instant};
|
||||
|
||||
-pub(super) struct Launch {
|
||||
- start: hipEvent_t,
|
||||
- end: hipEvent_t,
|
||||
- host: Instant,
|
||||
+#[derive(Clone, Copy)]
|
||||
+enum Mode { Off, Serial, Deferred(u64) }
|
||||
+fn mode() -> Mode {
|
||||
+ static MODE: OnceLock<Mode> = OnceLock::new();
|
||||
+ *MODE.get_or_init(|| {
|
||||
+ if std::env::var_os("D4R_ZLUDA_PROFILE_DEFERRED").is_some() {
|
||||
+ let every = std::env::var("D4R_ZLUDA_PROFILE_EVERY").ok()
|
||||
+ .and_then(|v| v.parse::<u64>().ok()).filter(|n| *n > 0).unwrap_or(17);
|
||||
+ Mode::Deferred(every)
|
||||
+ } else if std::env::var_os("D4R_ZLUDA_PROFILE").is_some() { Mode::Serial }
|
||||
+ else { Mode::Off }
|
||||
+ })
|
||||
+}
|
||||
+fn allow_legacy() -> bool {
|
||||
+ static ALLOW: OnceLock<bool> = OnceLock::new();
|
||||
+ *ALLOW.get_or_init(|| std::env::var_os("D4R_ZLUDA_PROFILE_ALLOW_LEGACY").is_some())
|
||||
}
|
||||
|
||||
-impl Drop for Launch {
|
||||
+struct Events { start: hipEvent_t, end: hipEvent_t }
|
||||
+impl Drop for Events {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.end.is_null() { let _ = hipEventDestroy(self.end); }
|
||||
@@ -17,44 +31,162 @@ impl Drop for Launch {
|
||||
}
|
||||
}
|
||||
}
|
||||
+impl Events {
|
||||
+ unsafe fn new() -> Result<Self, hipErrorCode_t> {
|
||||
+ let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut() };
|
||||
+ hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
+ hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
+ Ok(result)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+struct Description {
|
||||
+ name: CString, backend: String, phase: String, grid: [u32;3], block: [u32;3],
|
||||
+ shared: u32, lds: i32, regs: i32, private: i32, max_threads: i32, blocks_per_multiprocessor: i32,
|
||||
+}
|
||||
+impl Description {
|
||||
+ unsafe fn new(function: hipFunction_t, name: &CStr, backend: &str, phase: &str,
|
||||
+ grid: [u32;3], block: [u32;3], shared: u32) -> Result<Self, hipErrorCode_t> {
|
||||
+ let mut d = Self { name: name.to_owned(), backend: backend.to_owned(), phase: phase.to_owned(),
|
||||
+ grid, block, shared, lds: 0, regs: 0, private: 0, max_threads: 0, blocks_per_multiprocessor: 0 };
|
||||
+ // Copy metadata while the function/module is alive. Collection never
|
||||
+ // dereferences a function handle that the application may have unloaded.
|
||||
+ hipFuncGetAttribute(&mut d.lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
+ let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
+ .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut d.blocks_per_multiprocessor,
|
||||
+ function, threads as i32, shared as usize)?;
|
||||
+ Ok(d)
|
||||
+ }
|
||||
+ fn print(&self, gpu_ms: f32, enqueue_ms: f64, completion_ms: f64, serializing: bool, stream: usize) {
|
||||
+ if !gpu_ms.is_finite() || gpu_ms < 0. {
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_INVALID kernel={:?} backend={} phase={} gpu_ms={gpu_ms:.6} reason=invalid_elapsed_time serializing={} deferred={} stream=0x{stream:x}",
|
||||
+ self.name, self.backend, self.phase, u32::from(serializing), u32::from(!serializing));
|
||||
+ return;
|
||||
+ }
|
||||
+ eprintln!("D4R_KERNEL_PROFILE kernel={:?} backend={} phase={} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={} serializing={} deferred={} stream=0x{stream:x} legacy_stream={}",
|
||||
+ self.name, self.backend, self.phase, self.grid[0], self.grid[1], self.grid[2],
|
||||
+ self.block[0], self.block[1], self.block[2], self.lds, self.shared, self.regs,
|
||||
+ self.private, self.max_threads, self.blocks_per_multiprocessor,
|
||||
+ u32::from(serializing), u32::from(!serializing), u32::from(stream <= 1));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+struct Pending { launch: Launch, description: Description, enqueue_ms: f64, stream: usize }
|
||||
+const CAPACITY: usize = 64;
|
||||
+struct Deferred {
|
||||
+ calls: u64, skipped: u64, pending: Vec<Pending>, free: Vec<(i32, Events)>,
|
||||
+}
|
||||
+thread_local! {
|
||||
+ static DEFERRED: RefCell<Deferred> = RefCell::new(Deferred {
|
||||
+ calls: 0, skipped: 0, pending: Vec::new(), free: Vec::new(),
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+impl Deferred {
|
||||
+ unsafe fn collect(&mut self, device: i32) -> hipError_t {
|
||||
+ let mut index = 0;
|
||||
+ while index < self.pending.len() {
|
||||
+ let p = &self.pending[index];
|
||||
+ if p.launch.device != device { index += 1; continue; }
|
||||
+ match hipEventQuery(p.launch.events.end) {
|
||||
+ Err(e) if e == hipErrorCode_t::NotReady => { index += 1; continue; }
|
||||
+ result => result?,
|
||||
+ }
|
||||
+ let mut gpu_ms = 0.;
|
||||
+ hipEventElapsedTime(&mut gpu_ms, p.launch.events.start, p.launch.events.end)?;
|
||||
+ p.description.print(gpu_ms, p.enqueue_ms, p.launch.host.elapsed().as_secs_f64() * 1000., false, p.stream);
|
||||
+ let p = self.pending.swap_remove(index);
|
||||
+ // Re-record only after the old timestamps have been collected.
|
||||
+ self.free.push((device, p.launch.events));
|
||||
+ }
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/// Called only after the application's existing context synchronization, or
|
||||
+/// before a selected launch. No event/stream/device wait is added here.
|
||||
+pub(super) unsafe fn collect_completed() -> hipError_t {
|
||||
+ if !matches!(mode(), Mode::Deferred(_)) { return Ok(()); }
|
||||
+ DEFERRED.with(|state| {
|
||||
+ let mut state = state.borrow_mut();
|
||||
+ if state.pending.is_empty() { return Ok(()); }
|
||||
+ let mut device = 0; hipGetDevice(&mut device)?;
|
||||
+ state.collect(device)
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+pub(super) struct Launch {
|
||||
+ events: Events,
|
||||
+ host: Instant,
|
||||
+ device: i32,
|
||||
+ deferred: bool,
|
||||
+}
|
||||
|
||||
impl Launch {
|
||||
pub(super) unsafe fn begin(stream: hipStream_t) -> Result<Option<Self>, hipErrorCode_t> {
|
||||
- if std::env::var_os("D4R_ZLUDA_PROFILE").is_none() { return Ok(None); }
|
||||
- let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut(), host: Instant::now() };
|
||||
- hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
- hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
- hipEventRecord(result.start, stream)?;
|
||||
- result.host = Instant::now();
|
||||
- Ok(Some(result))
|
||||
+ let mode = mode();
|
||||
+ if matches!(mode, Mode::Off) { return Ok(None); }
|
||||
+ if let Mode::Deferred(every) = mode {
|
||||
+ return DEFERRED.with(|state| {
|
||||
+ let mut state = state.borrow_mut();
|
||||
+ state.calls = state.calls.wrapping_add(1);
|
||||
+ if (state.calls - 1) % every != 0 { return Ok(None); }
|
||||
+ // HIP timing events on the legacy default stream can order
|
||||
+ // other streams. Do not introduce that dependency in this mode.
|
||||
+ if stream.0 as usize <= 1 && !allow_legacy() {
|
||||
+ state.skipped += 1;
|
||||
+ if state.skipped.is_power_of_two() {
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=legacy_default_stream count={} serializing=0", state.skipped);
|
||||
+ }
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let mut device = 0; hipGetDevice(&mut device)?;
|
||||
+ state.collect(device)?;
|
||||
+ let mut capture = hipStreamCaptureStatus::hipStreamCaptureStatusNone;
|
||||
+ hipStreamIsCapturing(stream, &mut capture)?;
|
||||
+ if capture != hipStreamCaptureStatus::hipStreamCaptureStatusNone {
|
||||
+ state.skipped += 1;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=stream_capture count={} serializing=0", state.skipped);
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let events = if let Some(index) = state.free.iter().position(|(d, _)| *d == device) {
|
||||
+ state.free.swap_remove(index).1
|
||||
+ } else if state.pending.len() + state.free.len() < CAPACITY {
|
||||
+ Events::new()?
|
||||
+ } else {
|
||||
+ state.skipped += 1;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=capacity count={} capacity={CAPACITY} serializing=0", state.skipped);
|
||||
+ return Ok(None);
|
||||
+ };
|
||||
+ hipEventRecord(events.start, stream)?;
|
||||
+ Ok(Some(Self { events, host: Instant::now(), device, deferred: true }))
|
||||
+ });
|
||||
+ }
|
||||
+ let events = Events::new()?;
|
||||
+ hipEventRecord(events.start, stream)?;
|
||||
+ Ok(Some(Self { events, host: Instant::now(), device: -1, deferred: false }))
|
||||
}
|
||||
|
||||
pub(super) unsafe fn finish(self, function: hipFunction_t, name: &CStr, backend: &str,
|
||||
phase: &str, grid: [u32;3], block: [u32;3], shared: u32, stream: hipStream_t) -> hipError_t {
|
||||
let enqueue_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
- hipEventRecord(self.end, stream)?;
|
||||
- hipEventSynchronize(self.end)?;
|
||||
+ hipEventRecord(self.events.end, stream)?;
|
||||
+ let description = Description::new(function, name, backend, phase, grid, block, shared)?;
|
||||
+ if self.deferred {
|
||||
+ DEFERRED.with(|state| state.borrow_mut().pending.push(Pending {
|
||||
+ launch: self, description, enqueue_ms, stream: stream.0 as usize,
|
||||
+ }));
|
||||
+ return Ok(());
|
||||
+ }
|
||||
+ hipEventSynchronize(self.events.end)?;
|
||||
let completion_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
let mut gpu_ms = 0.;
|
||||
- hipEventElapsedTime(&mut gpu_ms, self.start, self.end)?;
|
||||
- // Module function handles use the Driver API attribute query. The
|
||||
- // plural Runtime API expects a registered host function address.
|
||||
- let mut lds = 0;
|
||||
- let mut regs = 0;
|
||||
- let mut private = 0;
|
||||
- let mut max_threads = 0;
|
||||
- hipFuncGetAttribute(&mut lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
- hipFuncGetAttribute(&mut regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
- hipFuncGetAttribute(&mut private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
- hipFuncGetAttribute(&mut max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
- let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
- .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
- let mut blocks_per_multiprocessor = 0;
|
||||
- hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut blocks_per_multiprocessor,
|
||||
- function, threads as i32, shared as usize)?;
|
||||
- eprintln!("D4R_KERNEL_PROFILE kernel={name:?} backend={backend} phase={phase} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={shared} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={blocks_per_multiprocessor} serializing=1",
|
||||
- grid[0], grid[1], grid[2], block[0], block[1], block[2], lds,
|
||||
- regs, private, max_threads);
|
||||
+ hipEventElapsedTime(&mut gpu_ms, self.events.start, self.events.end)?;
|
||||
+ description.print(gpu_ms, enqueue_ms, completion_ms, true, stream.0 as usize);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 232f7d4..8d43abf 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -503,6 +503,7 @@ fn zluda_build_version() -> &'static str {
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
"D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE" | "D4R_ZLUDA_PROFILE"
|
||||
+ | "D4R_ZLUDA_PROFILE_API" | "D4R_ZLUDA_PROFILE_DEFERRED" | "D4R_ZLUDA_PROFILE_EVERY" | "D4R_ZLUDA_PROFILE_ALLOW_LEGACY"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
From 67127dde599879ada022d6439383d682e09e0b36 Mon Sep 17 00:00:00 2001
|
||||
From: xdfnx-dev <[email protected]>
|
||||
Date: Fri, 2 Oct 2026 11:50:45 +0300
|
||||
Subject: [PATCH 17/17] Implement asynchronous 2D array copies for Windows
|
||||
input batching
|
||||
|
||||
---
|
||||
zluda/src/impl/memory.rs | 13 +++++++++++++
|
||||
zluda/src/lib.rs | 1 +
|
||||
2 files changed, 14 insertions(+)
|
||||
|
||||
diff --git a/zluda/src/impl/memory.rs b/zluda/src/impl/memory.rs
|
||||
index b724942..42facb3 100644
|
||||
--- a/zluda/src/impl/memory.rs
|
||||
+++ b/zluda/src/impl/memory.rs
|
||||
@@ -469,6 +469,19 @@ pub(crate) unsafe fn copy_2d_v2(memcpy: hip_Memcpy2D) -> CUresult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+pub(crate) unsafe fn copy_2d_async_v2(memcpy: hip_Memcpy2D, stream: hipStream_t) -> CUresult {
|
||||
+ // The format-80 emulation in copy_2d_v2 uses temporary CPU storage.
|
||||
+ // Reject it explicitly here: an asynchronous copy cannot outlive those
|
||||
+ // temporaries, and the Windows fast path uses canonical float arrays.
|
||||
+ let src_packed = memcpy.srcMemoryType == hipMemoryType::hipMemoryTypeArray
|
||||
+ && array::original_format(memcpy.srcArray)? == Some(80);
|
||||
+ let dst_packed = memcpy.dstMemoryType == hipMemoryType::hipMemoryTypeArray
|
||||
+ && array::original_format(memcpy.dstArray)? == Some(80);
|
||||
+ if src_packed || dst_packed { return Err(CUerror::NOT_SUPPORTED); }
|
||||
+ hipMemcpyParam2DAsync(&memcpy, stream)?;
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
pub(crate) unsafe fn copy_2d_unaligned_v2(memcpy: hip_Memcpy2D) -> CUresult {
|
||||
hipDrvMemcpy2DUnaligned(&memcpy)?;
|
||||
Ok(())
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
index 31bfa42..939981e 100644
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -150,6 +150,7 @@ cuda_macros::cuda_function_declarations!(
|
||||
cuMemAlloc_v2,
|
||||
cuMemAllocPitch_v2,
|
||||
cuMemcpy2D_v2,
|
||||
+ cuMemcpy2DAsync_v2,
|
||||
cuMemcpy2DUnaligned_v2,
|
||||
cuMemcpy3D_v2,
|
||||
cuMemcpyAsync,
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
Reference in new issue
Block a user