vyre_driver_wgpu/engine/
streaming.rs1use std::sync::{Arc, LazyLock};
24
25use crossbeam_channel::{bounded, Receiver, Sender};
26use vyre_driver::{BackendError, CompiledPipeline, DispatchConfig};
27
28use crate::pipeline::WgpuPipeline;
29use crate::thread_pool::{BoundedWorkerJob, BoundedWorkerPool};
30
31pub mod async_copy;
33
34pub struct HostIngressStream {
40 runner:
41 Arc<dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync>,
42 config: DispatchConfig,
43 in_flight: Option<Receiver<Result<Vec<Vec<u8>>, BackendError>>>,
44}
45
46type ChunkResult = Result<Vec<Vec<u8>>, BackendError>;
47
48struct ChunkJob {
49 runner:
50 Arc<dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync>,
51 bytes: Vec<u8>,
52 config: DispatchConfig,
53 response: Sender<ChunkResult>,
54}
55
56struct StreamingPool {
57 pool: BoundedWorkerPool<ChunkJob>,
58}
59
60impl StreamingPool {
61 fn global() -> Result<&'static Self, BackendError> {
62 static POOL: LazyLock<Result<StreamingPool, BackendError>> =
63 LazyLock::new(StreamingPool::new);
64 POOL.as_ref().map_err(|e| BackendError::new(e.to_string()))
65 }
66
67 fn new() -> Result<Self, BackendError> {
68 const JOB_QUEUE: usize = 64;
69 Ok(Self {
70 pool: BoundedWorkerPool::new(
71 JOB_QUEUE,
72 "vyre-wgpu-streaming",
73 "inspect the chunk program and GPU driver logs.",
74 "reduce process thread count or increase system nproc limit.",
75 )?,
76 })
77 }
78
79 fn submit(
80 &self,
81 runner: Arc<
82 dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync,
83 >,
84 bytes: Vec<u8>,
85 config: DispatchConfig,
86 ) -> Result<Receiver<ChunkResult>, BackendError> {
87 let (sender, receiver) = bounded(1);
88 let job = ChunkJob {
89 runner,
90 bytes,
91 config,
92 response: sender,
93 };
94 self.pool.submit_blocking(
95 job,
96 "recreate the process; the global stream pool only closes during shutdown.",
97 )?;
98 Ok(receiver)
99 }
100}
101
102impl BoundedWorkerJob for ChunkJob {
103 type Output = Vec<Vec<u8>>;
104
105 fn response(&self) -> &Sender<ChunkResult> {
106 &self.response
107 }
108
109 fn run(self) -> ChunkResult {
110 (self.runner)(self.bytes, self.config)
111 }
112}
113
114impl HostIngressStream {
115 #[must_use]
117 pub fn new(pipeline: WgpuPipeline, config: DispatchConfig) -> Self {
118 let runner = Arc::new(move |bytes: Vec<u8>, config: DispatchConfig| {
119 pipeline.dispatch(&[bytes], &config)
120 });
121 Self {
122 runner,
123 config,
124 in_flight: None,
125 }
126 }
127
128 #[must_use]
130 pub fn from_runner<F>(runner: F, config: DispatchConfig) -> Self
131 where
132 F: Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError>
133 + Send
134 + Sync
135 + 'static,
136 {
137 Self {
138 runner: Arc::new(runner),
139 config,
140 in_flight: None,
141 }
142 }
143
144 pub fn push_chunk(&mut self, bytes: Vec<u8>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
152 let previous = self.take_finished()?;
153 let runner = Arc::clone(&self.runner);
154 let config = self.config.clone();
155 self.in_flight = Some(StreamingPool::global()?.submit(runner, bytes, config)?);
156 Ok(previous)
157 }
158
159 pub fn finish(&mut self) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
165 self.take_finished()
166 }
167
168 fn take_finished(&mut self) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
169 let Some(handle) = self.in_flight.take() else {
170 return Ok(None);
171 };
172 handle.recv().map_err(|error| {
173 BackendError::new(
174 format!("host-ingress worker ended before sending a result: {error}. Fix: inspect worker-pool lifecycle and GPU driver logs."),
175 )
176 })?
177 .map(Some)
178 }
179}