use crate::memory::manager::WgpuMemManager;
use super::{poll::WgpuPoll, timings::QueryProfiler};
use crate::{WgpuResource, memory::controller::WgpuAllocController, schedule::ScheduleTask};
use ruda_core::{
backtrace::BackTrace,
bytes::Bytes,
profile::{ProfileDuration, TimingMethod},
};
use ruda_kernel::dsl::{
RudaCount, MemoryConfiguration,
future::{self, DynFut},
server::{IoError, ProfileError, ProfilingToken, ServerError, StreamErrorMode},
zspace::Shape,
};
use ruda_core::ir::MemoryDeviceProperties;
use ruda::runtime::{
logging::ServerLogger, memory_management::ManagedMemoryHandle,
timestamp_profiler::TimestampProfiler,
};
use std::{future::Future, num::NonZero, pin::Pin, sync::Arc};
use wgpu::ComputePipeline;
#[derive(Debug)]
enum Timings {
Device(QueryProfiler),
System(TimestampProfiler),
}
#[derive(Debug)]
pub struct WgpuStream {
pub mem_manage: WgpuMemManager,
pub device: wgpu::Device,
pub errors: Vec<ServerError>,
compute_pass: Option<wgpu::ComputePass<'static>>,
timings: Timings,
tasks_count: usize,
tasks_max: usize,
queue: wgpu::Queue,
encoder: wgpu::CommandEncoder,
poll: WgpuPoll,
submission_load: SubmissionLoad,
pending_write_count: usize,
}
impl WgpuStream {
pub fn new(
device: wgpu::Device,
queue: wgpu::Queue,
memory_properties: MemoryDeviceProperties,
memory_config: MemoryConfiguration,
timing_method: TimingMethod,
tasks_max: usize,
logger: Arc<ServerLogger>,
) -> Self {
let timings = if timing_method == TimingMethod::Device {
Timings::Device(QueryProfiler::new(&queue, &device))
} else {
if cfg!(target_family = "wasm") {
panic!(
"Cannot profile on web assembly without timestamp_query feature as it requires blocking."
);
}
Timings::System(TimestampProfiler::default())
};
let poll = WgpuPoll::new(device.clone());
#[allow(unused_mut)]
let mut mem_manage =
WgpuMemManager::new(device.clone(), memory_properties, memory_config, logger);
Self {
mem_manage,
compute_pass: None,
timings,
errors: Vec::new(),
encoder: {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Ruda Tasks Encoder"),
})
},
device,
queue,
tasks_count: 0,
tasks_max,
poll,
submission_load: SubmissionLoad::default(),
pending_write_count: 0,
}
}
pub fn enqueue_task(&mut self, task: ScheduleTask) {
match task {
ScheduleTask::Write { data, buffer } => {
let _ = self
.flush(StreamErrorMode {
ignore: true,
flush: false,
})
.ok();
self.write_to_buffer(&buffer, &data);
}
ScheduleTask::Execute {
pipeline,
count,
resources,
} => {
let resources = resources.into_resources(self);
self.register_pipeline(pipeline, resources.iter(), &count);
}
}
}
pub fn sync(
&mut self,
) -> Pin<Box<dyn Future<Output = Result<(), ServerError>> + Send + 'static>> {
let error_scope = self.device.push_error_scope(wgpu::ErrorFilter::Internal);
let flush_error = self
.flush(StreamErrorMode {
ignore: false,
flush: true,
})
.err();
let queue = self.queue.clone();
let error_future = error_scope.pop();
let poll = self.poll.start_polling();
Box::pin(async move {
let (sender, receiver) = async_channel::bounded::<()>(1);
queue.on_submitted_work_done(move || {
let _ = sender.try_send(());
core::mem::drop(poll);
});
let _ = receiver.recv().await;
if let Some(error) = error_future.await {
return Err(ServerError::Generic {
reason: format!("{error}"),
backtrace: BackTrace::capture(),
});
}
match flush_error {
Some(err) => Err(err),
None => Ok(()),
}
})
}
pub fn empty(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
self.mem_manage.reserve(size)
}
pub fn error(&mut self, error: ServerError) {
self.errors.push(error);
}
pub(crate) fn create_uniform(&mut self, data: &[u8]) -> WgpuResource {
let resource = self.mem_manage.reserve_uniform(data.len() as u64);
self.write_to_buffer(&resource, data);
resource
}
fn flush_if_needed(&mut self) {
if self.tasks_count >= self.tasks_max {
let _ = self
.flush(StreamErrorMode {
ignore: true,
flush: false,
})
.ok();
}
}
pub fn flush(&mut self, mode: StreamErrorMode) -> Result<(), ServerError> {
if self.tasks_count == 0 {
return self.flush_errors(mode);
}
self.compute_pass = None;
let tasks_encoder = {
std::mem::replace(&mut self.encoder, {
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Ruda Tasks Encoder"),
})
})
};
let index = self.queue.submit([tasks_encoder.finish()]);
self.submission_load
.regulate(&self.device, self.tasks_count, index);
self.mem_manage.memory_cleanup(false);
self.mem_manage.release_uniforms();
self.tasks_count = 0;
self.pending_write_count = 0;
self.flush_errors(mode)
}
fn flush_errors(&mut self, mode: StreamErrorMode) -> Result<(), ServerError> {
if mode.flush {
let errors = self.flush_errors_queue();
if !mode.ignore && !errors.is_empty() {
let error = ServerError::ServerUnhealthy {
errors,
backtrace: BackTrace::capture(),
};
return Err(error);
}
} else if !mode.ignore && !self.errors.is_empty() {
let error = ServerError::ServerUnhealthy {
errors: self.errors.clone(),
backtrace: BackTrace::capture(),
};
return Err(error);
}
Ok(())
}
fn register_pipeline<'a>(
&mut self,
pipeline: Arc<ComputePipeline>,
resources: impl Iterator<Item = &'a WgpuResource>,
dispatch: &RudaCount,
) {
if dispatch.is_empty() {
return;
}
let entries = resources
.enumerate()
.map(|(i, r)| wgpu::BindGroupEntry {
binding: i as u32,
resource: r.as_wgpu_bind_resource(),
})
.collect::<Vec<_>>();
let pass = self.compute_pass.get_or_insert_with(|| {
let writes = if let Timings::Device(query_time) = &mut self.timings {
query_time
.register_profile_device(&self.device)
.map(|query_set| wgpu::ComputePassTimestampWrites {
query_set,
beginning_of_pass_write_index: Some(0),
end_of_pass_write_index: Some(1),
})
} else {
None
};
self.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: None,
timestamp_writes: writes,
})
.forget_lifetime()
});
self.tasks_count += 1;
let group_layout = pipeline.get_bind_group_layout(0);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &group_layout,
entries: &entries,
});
pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &bind_group, &[]);
match dispatch.clone() {
RudaCount::Static(x, y, z) => {
pass.dispatch_workgroups(x, y, z);
}
RudaCount::Dynamic(binding) => {
let res = self.mem_manage.get_resource(binding).unwrap();
pass.dispatch_workgroups_indirect(&res.buffer, res.offset);
}
}
self.flush_if_needed();
}
pub(crate) fn flush_errors_queue(&mut self) -> Vec<ServerError> {
let errors = core::mem::take(&mut self.errors);
if !errors.is_empty() {
self.profile_error(ProfileError::Unknown {
reason: alloc::format!("{:?}", errors),
backtrace: BackTrace::capture(),
});
}
errors
}
}
mod io;
mod profiling;
mod submission;
use submission::*;