use std::sync::Arc;
use crate::context::Context;
use crate::error::CudaResult;
use crate::event::Event;
use crate::ffi::{CU_STREAM_NON_BLOCKING, CUcontext, CUstream};
use crate::loader::try_driver;
fn create_stream_in_ctx(
api: &crate::loader::DriverApi,
ctx: &Context,
create: impl FnOnce(&mut CUstream) -> u32,
) -> CudaResult<CUstream> {
let mut prev = CUcontext::default();
let _ = unsafe { (api.cu_ctx_get_current)(&mut prev) };
crate::cuda_call!((api.cu_ctx_set_current)(ctx.raw()))?;
let mut raw = CUstream::default();
let rc = create(&mut raw);
let _ = unsafe { (api.cu_ctx_set_current)(prev) };
crate::error::check(rc)?;
Ok(raw)
}
pub struct Stream {
raw: CUstream,
ctx: Arc<Context>,
}
impl Stream {
pub fn new(ctx: &Arc<Context>) -> CudaResult<Self> {
let api = try_driver()?;
let raw = create_stream_in_ctx(api, ctx, |raw| unsafe {
(api.cu_stream_create)(raw, CU_STREAM_NON_BLOCKING)
})?;
Ok(Self {
raw,
ctx: Arc::clone(ctx),
})
}
pub fn with_priority(ctx: &Arc<Context>, priority: i32) -> CudaResult<Self> {
let api = try_driver()?;
let raw = create_stream_in_ctx(api, ctx, |raw| unsafe {
(api.cu_stream_create_with_priority)(raw, CU_STREAM_NON_BLOCKING, priority)
})?;
Ok(Self {
raw,
ctx: Arc::clone(ctx),
})
}
pub fn synchronize(&self) -> CudaResult<()> {
let api = try_driver()?;
crate::cuda_call!((api.cu_stream_synchronize)(self.raw))
}
pub fn wait_event(&self, event: &Event) -> CudaResult<()> {
let api = try_driver()?;
crate::cuda_call!((api.cu_stream_wait_event)(self.raw, event.raw(), 0))
}
#[inline]
pub fn raw(&self) -> CUstream {
self.raw
}
#[inline]
pub fn context(&self) -> &Arc<Context> {
&self.ctx
}
}
impl Drop for Stream {
fn drop(&mut self) {
if let Ok(api) = try_driver() {
let rc = unsafe { (api.cu_stream_destroy_v2)(self.raw) };
if rc != 0 {
tracing::warn!(
cuda_error = rc,
stream = ?self.raw,
"cuStreamDestroy_v2 failed during drop"
);
}
}
}
}
#[cfg(test)]
mod multi_stream_tests {
use super::*;
use crate::device::Device;
use crate::ffi::CUdeviceptr;
use crate::module::Module;
use std::ffi::c_void;
const DOUBLE_PTX: &str = "\
.version 7.0
.target sm_70
.address_size 64
.visible .entry dbl(
.param .u64 ptr,
.param .u32 n
)
{
.reg .b32 %r<8>;
.reg .b64 %rd<8>;
.reg .f32 %f<2>;
.reg .pred %p<2>;
ld.param.u64 %rd0, [ptr];
ld.param.u32 %r0, [n];
mov.u32 %r1, %ctaid.x;
mov.u32 %r2, %ntid.x;
mov.u32 %r3, %tid.x;
mad.lo.u32 %r4, %r1, %r2, %r3;
mov.u32 %r5, %nctaid.x;
mul.lo.u32 %r6, %r5, %r2;
$LOOP:
setp.ge.u32 %p0, %r4, %r0;
@%p0 bra $DONE;
mul.wide.u32 %rd1, %r4, 4;
add.u64 %rd2, %rd0, %rd1;
ld.global.f32 %f0, [%rd2];
add.f32 %f0, %f0, %f0;
st.global.f32 [%rd2], %f0;
add.u32 %r4, %r4, %r6;
bra $LOOP;
$DONE:
ret;
}
";
fn launch_double(
api: &crate::loader::DriverApi,
func: &crate::module::Function,
stream: &Stream,
dptr: CUdeviceptr,
n: usize,
) -> CudaResult<()> {
let mut dptr_arg = dptr;
let mut n_arg: u32 = n as u32;
let mut params: [*mut c_void; 2] = [
(&mut dptr_arg as *mut CUdeviceptr).cast(),
(&mut n_arg as *mut u32).cast(),
];
crate::error::check(unsafe {
(api.cu_launch_kernel)(
func.raw(),
8,
1,
1,
128,
1,
1,
0,
stream.raw(),
params.as_mut_ptr(),
std::ptr::null_mut(),
)
})
}
#[test]
fn two_streams_concurrent_and_cross_stream_event() {
let Ok(dev) = Device::get(0) else {
return;
};
let ctx = match Context::new(&dev) {
Ok(c) => Arc::new(c),
Err(_) => return,
};
let stream_a = match Stream::new(&ctx) {
Ok(s) => s,
Err(_) => return,
};
let stream_b = match Stream::new(&ctx) {
Ok(s) => s,
Err(_) => return,
};
let api = try_driver().expect("driver present");
let module = match Module::from_ptx(DOUBLE_PTX) {
Ok(m) => m,
Err(_) => return,
};
let func = module.get_function("dbl").expect("dbl");
const N: usize = 2048;
let bytes = N * std::mem::size_of::<f32>();
let a_in: Vec<f32> = (0..N).map(|i| i as f32).collect();
let b_in: Vec<f32> = (0..N).map(|i| i as f32 + 1000.0).collect();
let mut da: CUdeviceptr = 0;
let mut db: CUdeviceptr = 0;
crate::error::check(unsafe { (api.cu_mem_alloc_v2)(&mut da, bytes) }).expect("alloc a");
crate::error::check(unsafe { (api.cu_mem_alloc_v2)(&mut db, bytes) }).expect("alloc b");
let result = (|| -> CudaResult<(Vec<f32>, Vec<f32>)> {
crate::error::check(unsafe {
(api.cu_memcpy_htod_v2)(da, a_in.as_ptr().cast(), bytes)
})?;
crate::error::check(unsafe {
(api.cu_memcpy_htod_v2)(db, b_in.as_ptr().cast(), bytes)
})?;
launch_double(api, &func, &stream_a, da, N)?;
launch_double(api, &func, &stream_b, db, N)?;
let evt = Event::new()?;
evt.record(&stream_a)?;
stream_b.wait_event(&evt)?;
launch_double(api, &func, &stream_b, da, N)?;
stream_a.synchronize()?;
stream_b.synchronize()?;
let mut a_out = vec![0.0f32; N];
let mut b_out = vec![0.0f32; N];
crate::error::check(unsafe {
(api.cu_memcpy_dtoh_v2)(a_out.as_mut_ptr().cast(), da, bytes)
})?;
crate::error::check(unsafe {
(api.cu_memcpy_dtoh_v2)(b_out.as_mut_ptr().cast(), db, bytes)
})?;
Ok((a_out, b_out))
})();
let _ = unsafe { (api.cu_mem_free_v2)(da) };
let _ = unsafe { (api.cu_mem_free_v2)(db) };
let (a_out, b_out) = result.expect("multi-stream round-trip");
for (i, &v) in a_out.iter().enumerate() {
assert!(
(v - 4.0 * i as f32).abs() <= 1e-4,
"stream A buffer element {i}: got {v}, expected {}",
4.0 * i as f32
);
}
for (i, &v) in b_out.iter().enumerate() {
let want = 2.0 * (i as f32 + 1000.0);
assert!(
(v - want).abs() <= 1e-3,
"stream B buffer element {i}: got {v}, expected {want}"
);
}
}
}