Skip to main content

ab_farmer/plotter/
gpu.rs

1//! GPU plotter
2
3mod gpu_encoders_manager;
4pub mod metrics;
5
6use crate::plotter::gpu::gpu_encoders_manager::GpuRecordsEncoderManager;
7use crate::plotter::gpu::metrics::GpuPlotterMetrics;
8use crate::plotter::{Plotter, SectorPlottingProgress};
9use crate::utils::AsyncJoinOnDrop;
10use ab_core_primitives::ed25519::Ed25519PublicKey;
11use ab_core_primitives::sectors::SectorIndex;
12use ab_core_primitives::solutions::ShardCommitmentHash;
13use ab_data_retrieval::piece_getter::PieceGetter;
14use ab_erasure_coding::ErasureCoding;
15use ab_farmer_components::FarmerProtocolInfo;
16use ab_farmer_components::plotting::{
17    DownloadSectorOptions, EncodeSectorOptions, PlottingError, download_sector, encode_sector,
18    write_sector,
19};
20use ab_proof_of_space_gpu::GpuRecordsEncoder;
21use async_lock::{Mutex as AsyncMutex, Semaphore, SemaphoreGuardArc};
22use async_trait::async_trait;
23use bytes::Bytes;
24use event_listener_primitives::{Bag, HandlerId};
25use futures::channel::mpsc;
26use futures::stream::FuturesUnordered;
27use futures::{FutureExt, Sink, SinkExt, StreamExt, select, stream};
28use prometheus_client::registry::Registry;
29use std::error::Error;
30use std::fmt;
31use std::future::pending;
32use std::num::TryFromIntError;
33use std::pin::pin;
34use std::sync::Arc as StdArc;
35use std::sync::atomic::{AtomicBool, Ordering};
36use std::task::Poll;
37use std::time::Instant;
38use tokio::task::yield_now;
39use tracing::{Instrument, warn};
40use triomphe::Arc;
41
42/// Type alias used for event handlers
43pub type HandlerFn3<A, B, C> = StdArc<dyn Fn(&A, &B, &C) + Send + Sync + 'static>;
44type Handler3<A, B, C> = Bag<HandlerFn3<A, B, C>, A, B, C>;
45
46#[derive(Default, Debug)]
47struct Handlers {
48    plotting_progress: Handler3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
49}
50
51/// GPU plotter
52pub struct GpuPlotter<PG> {
53    piece_getter: PG,
54    downloading_semaphore: StdArc<Semaphore>,
55    gpu_records_encoders_manager: GpuRecordsEncoderManager,
56    global_mutex: Arc<AsyncMutex<()>>,
57    erasure_coding: ErasureCoding,
58    handlers: Arc<Handlers>,
59    tasks_sender: mpsc::Sender<AsyncJoinOnDrop<()>>,
60    _background_tasks: AsyncJoinOnDrop<()>,
61    abort_early: Arc<AtomicBool>,
62    metrics: Option<Arc<GpuPlotterMetrics>>,
63}
64
65impl<PG> fmt::Debug for GpuPlotter<PG> {
66    #[inline]
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.debug_struct("GpuPlotter").finish_non_exhaustive()
69    }
70}
71
72impl<PG> Drop for GpuPlotter<PG> {
73    #[inline]
74    fn drop(&mut self) {
75        self.abort_early.store(true, Ordering::Release);
76        self.tasks_sender.close_channel();
77    }
78}
79
80#[async_trait]
81impl<PG> Plotter for GpuPlotter<PG>
82where
83    PG: PieceGetter + Clone + Send + Sync + 'static,
84{
85    async fn has_free_capacity(&self) -> Result<bool, String> {
86        Ok(self.downloading_semaphore.try_acquire().is_some())
87    }
88
89    async fn plot_sector(
90        &self,
91        public_key: Ed25519PublicKey,
92        shard_commitments_root: ShardCommitmentHash,
93        sector_index: SectorIndex,
94        farmer_protocol_info: FarmerProtocolInfo,
95        pieces_in_sector: u16,
96        _replotting: bool,
97        progress_sender: mpsc::Sender<SectorPlottingProgress>,
98    ) {
99        let start = Instant::now();
100
101        // Done outside the future below as a backpressure, ensuring that it is not possible to
102        // schedule unbounded number of plotting tasks
103        let downloading_permit = self.downloading_semaphore.acquire_arc().await;
104
105        self.plot_sector_internal(
106            start,
107            downloading_permit,
108            public_key,
109            shard_commitments_root,
110            sector_index,
111            farmer_protocol_info,
112            pieces_in_sector,
113            progress_sender,
114        )
115        .await;
116    }
117
118    async fn try_plot_sector(
119        &self,
120        public_key: Ed25519PublicKey,
121        shard_commitments_root: ShardCommitmentHash,
122        sector_index: SectorIndex,
123        farmer_protocol_info: FarmerProtocolInfo,
124        pieces_in_sector: u16,
125        _replotting: bool,
126        progress_sender: mpsc::Sender<SectorPlottingProgress>,
127    ) -> bool {
128        let start = Instant::now();
129
130        let Some(downloading_permit) = self.downloading_semaphore.try_acquire_arc() else {
131            return false;
132        };
133
134        self.plot_sector_internal(
135            start,
136            downloading_permit,
137            public_key,
138            shard_commitments_root,
139            sector_index,
140            farmer_protocol_info,
141            pieces_in_sector,
142            progress_sender,
143        )
144        .await;
145
146        true
147    }
148}
149
150impl<PG> GpuPlotter<PG>
151where
152    PG: PieceGetter + Clone + Send + Sync + 'static,
153{
154    /// Create a new instance.
155    ///
156    /// Returns an error if empty list of encoders is provided.
157    pub fn new(
158        piece_getter: PG,
159        downloading_semaphore: StdArc<Semaphore>,
160        gpu_records_encoders: Vec<GpuRecordsEncoder>,
161        global_mutex: Arc<AsyncMutex<()>>,
162        erasure_coding: ErasureCoding,
163        registry: Option<&mut Registry>,
164    ) -> Result<Self, TryFromIntError> {
165        let (tasks_sender, mut tasks_receiver) = mpsc::channel(1);
166
167        // Basically runs plotting tasks in the background and allows to abort on drop
168        let background_tasks = AsyncJoinOnDrop::new(
169            tokio::spawn(async move {
170                let background_tasks = FuturesUnordered::new();
171                let mut background_tasks = pin!(background_tasks);
172                // Just so that `FuturesUnordered` will never end
173                background_tasks.push(AsyncJoinOnDrop::new(tokio::spawn(pending::<()>()), true));
174
175                loop {
176                    select! {
177                        maybe_background_task = tasks_receiver.next().fuse() => {
178                            let Some(background_task) = maybe_background_task else {
179                                break;
180                            };
181
182                            background_tasks.push(background_task);
183                        },
184                        _ = background_tasks.select_next_some() => {
185                            // Nothing to do
186                        }
187                    }
188                }
189            }),
190            true,
191        );
192
193        let abort_early = Arc::new(AtomicBool::new(false));
194        let gpu_records_encoders_manager = GpuRecordsEncoderManager::new(gpu_records_encoders)?;
195        let metrics = registry.map(|registry| {
196            Arc::new(GpuPlotterMetrics::new(
197                registry,
198                gpu_records_encoders_manager.gpu_records_encoders(),
199            ))
200        });
201
202        Ok(Self {
203            piece_getter,
204            downloading_semaphore,
205            gpu_records_encoders_manager,
206            global_mutex,
207            erasure_coding,
208            handlers: Arc::default(),
209            tasks_sender,
210            _background_tasks: background_tasks,
211            abort_early,
212            metrics,
213        })
214    }
215
216    /// Subscribe to plotting progress notifications
217    pub fn on_plotting_progress(
218        &self,
219        callback: HandlerFn3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
220    ) -> HandlerId {
221        self.handlers.plotting_progress.add(callback)
222    }
223
224    #[expect(clippy::too_many_arguments, reason = "Internal API")]
225    async fn plot_sector_internal<PS>(
226        &self,
227        start: Instant,
228        downloading_permit: SemaphoreGuardArc,
229        public_key: Ed25519PublicKey,
230        shard_commitments_root: ShardCommitmentHash,
231        sector_index: SectorIndex,
232        farmer_protocol_info: FarmerProtocolInfo,
233        pieces_in_sector: u16,
234        mut progress_sender: PS,
235    ) where
236        PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
237        PS::Error: Error,
238    {
239        if let Some(metrics) = &self.metrics {
240            metrics.sector_plotting.inc();
241        }
242
243        let progress_updater = ProgressUpdater {
244            public_key,
245            sector_index,
246            handlers: Arc::clone(&self.handlers),
247            metrics: self.metrics.clone(),
248        };
249
250        let plotting_fut = {
251            let piece_getter = self.piece_getter.clone();
252            let gpu_records_encoders_manager = self.gpu_records_encoders_manager.clone();
253            let global_mutex = Arc::clone(&self.global_mutex);
254            let erasure_coding = self.erasure_coding.clone();
255            let abort_early = Arc::clone(&self.abort_early);
256            let metrics = self.metrics.clone();
257
258            async move {
259                // Downloading
260                let downloaded_sector = {
261                    if !progress_updater
262                        .update_progress_and_events(
263                            &mut progress_sender,
264                            SectorPlottingProgress::Downloading,
265                        )
266                        .await
267                    {
268                        return;
269                    }
270
271                    // Take mutex briefly to make sure plotting is allowed right now
272                    global_mutex.lock().await;
273
274                    let downloading_start = Instant::now();
275                    let public_key_hash = &public_key.hash();
276
277                    let downloaded_sector_fut = download_sector(DownloadSectorOptions {
278                        public_key_hash,
279                        shard_commitments_root: &shard_commitments_root,
280                        sector_index,
281                        piece_getter: &piece_getter,
282                        farmer_protocol_info,
283                        erasure_coding: &erasure_coding,
284                        pieces_in_sector,
285                    });
286
287                    let downloaded_sector = match downloaded_sector_fut.await {
288                        Ok(downloaded_sector) => downloaded_sector,
289                        Err(error) => {
290                            warn!(%error, "Failed to download sector");
291
292                            progress_updater
293                                .update_progress_and_events(
294                                    &mut progress_sender,
295                                    SectorPlottingProgress::Error {
296                                        error: format!("Failed to download sector: {error}"),
297                                    },
298                                )
299                                .await;
300
301                            return;
302                        }
303                    };
304
305                    if !progress_updater
306                        .update_progress_and_events(
307                            &mut progress_sender,
308                            SectorPlottingProgress::Downloaded(downloading_start.elapsed()),
309                        )
310                        .await
311                    {
312                        return;
313                    }
314
315                    downloaded_sector
316                };
317
318                // Plotting
319                let (sector, plotted_sector) = {
320                    let mut records_encoder = gpu_records_encoders_manager.get_encoder().await;
321                    if let Some(metrics) = &metrics {
322                        metrics.plotting_capacity_used.inc();
323                    }
324
325                    // Give a chance to interrupt plotting if necessary
326                    yield_now().await;
327
328                    if !progress_updater
329                        .update_progress_and_events(
330                            &mut progress_sender,
331                            SectorPlottingProgress::Encoding,
332                        )
333                        .await
334                    {
335                        if let Some(metrics) = &metrics {
336                            metrics.plotting_capacity_used.dec();
337                        }
338                        return;
339                    }
340
341                    let encoding_start = Instant::now();
342
343                    let plotting_result = tokio::task::block_in_place(move || {
344                        let encoded_sector = encode_sector(
345                            downloaded_sector,
346                            EncodeSectorOptions {
347                                sector_index,
348                                records_encoder: &mut *records_encoder,
349                                abort_early: &abort_early,
350                            },
351                        )?;
352
353                        if abort_early.load(Ordering::Acquire) {
354                            return Err(PlottingError::AbortEarly);
355                        }
356
357                        drop(records_encoder);
358
359                        let mut sector = Vec::new();
360
361                        write_sector(&encoded_sector, &mut sector)?;
362
363                        Ok((sector, encoded_sector.plotted_sector))
364                    });
365
366                    if let Some(metrics) = &metrics {
367                        metrics.plotting_capacity_used.dec();
368                    }
369
370                    match plotting_result {
371                        Ok(plotting_result) => {
372                            if !progress_updater
373                                .update_progress_and_events(
374                                    &mut progress_sender,
375                                    SectorPlottingProgress::Encoded(encoding_start.elapsed()),
376                                )
377                                .await
378                            {
379                                return;
380                            }
381
382                            plotting_result
383                        }
384                        Err(PlottingError::AbortEarly) => {
385                            return;
386                        }
387                        Err(error) => {
388                            progress_updater
389                                .update_progress_and_events(
390                                    &mut progress_sender,
391                                    SectorPlottingProgress::Error {
392                                        error: format!("Failed to encode sector: {error}"),
393                                    },
394                                )
395                                .await;
396
397                            return;
398                        }
399                    }
400                };
401
402                progress_updater
403                    .update_progress_and_events(
404                        &mut progress_sender,
405                        SectorPlottingProgress::Finished {
406                            plotted_sector,
407                            time: start.elapsed(),
408                            sector: Box::pin({
409                                let mut sector = Some(Ok(Bytes::from(sector)));
410
411                                stream::poll_fn(move |_cx| {
412                                    // Just so that permit is dropped with stream itself
413                                    let _downloading_permit = &downloading_permit;
414
415                                    Poll::Ready(sector.take())
416                                })
417                            }),
418                        },
419                    )
420                    .await;
421            }
422        };
423
424        // Spawn a separate task such that `block_in_place` inside will not affect anything else
425        let plotting_task =
426            AsyncJoinOnDrop::new(tokio::spawn(plotting_fut.in_current_span()), true);
427        if let Err(error) = self.tasks_sender.clone().send(plotting_task).await {
428            warn!(%error, "Failed to send plotting task");
429
430            let progress = SectorPlottingProgress::Error {
431                error: format!("Failed to send plotting task: {error}"),
432            };
433
434            self.handlers
435                .plotting_progress
436                .call_simple(&public_key, &sector_index, &progress);
437        }
438    }
439}
440
441struct ProgressUpdater {
442    public_key: Ed25519PublicKey,
443    sector_index: SectorIndex,
444    handlers: Arc<Handlers>,
445    metrics: Option<Arc<GpuPlotterMetrics>>,
446}
447
448impl ProgressUpdater {
449    /// Returns `true` on success and `false` if progress receiver channel is gone
450    async fn update_progress_and_events<PS>(
451        &self,
452        progress_sender: &mut PS,
453        progress: SectorPlottingProgress,
454    ) -> bool
455    where
456        PS: Sink<SectorPlottingProgress> + Unpin,
457        PS::Error: Error,
458    {
459        if let Some(metrics) = &self.metrics {
460            #[expect(
461                clippy::rest_pattern_accessible_field,
462                reason = "Do not need other fields"
463            )]
464            match &progress {
465                SectorPlottingProgress::Downloading => {
466                    metrics.sector_downloading.inc();
467                }
468                SectorPlottingProgress::Downloaded(time) => {
469                    metrics.sector_downloading_time.observe(time.as_secs_f64());
470                    metrics.sector_downloaded.inc();
471                }
472                SectorPlottingProgress::Encoding => {
473                    metrics.sector_encoding.inc();
474                }
475                SectorPlottingProgress::Encoded(time) => {
476                    metrics.sector_encoding_time.observe(time.as_secs_f64());
477                    metrics.sector_encoded.inc();
478                }
479                SectorPlottingProgress::Finished { time, .. } => {
480                    metrics.sector_plotting_time.observe(time.as_secs_f64());
481                    metrics.sector_plotted.inc();
482                }
483                SectorPlottingProgress::Error { .. } => {
484                    metrics.sector_plotting_error.inc();
485                }
486            }
487        }
488        self.handlers.plotting_progress.call_simple(
489            &self.public_key,
490            &self.sector_index,
491            &progress,
492        );
493
494        if let Err(error) = progress_sender.send(progress).await {
495            warn!(%error, "Failed to send progress update");
496
497            false
498        } else {
499            true
500        }
501    }
502}