subspace_farmer/plotter/gpu/
wgpu.rs1use crate::plotter::gpu::GpuRecordsEncoder;
4use async_lock::Mutex as AsyncMutex;
5use parking_lot::Mutex;
6use rayon::{ThreadPool, ThreadPoolBuildError, ThreadPoolBuilder, current_thread_index};
7use std::fmt;
8use std::process::exit;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11use subspace_core_primitives::pieces::{PieceOffset, Record};
12use subspace_core_primitives::sectors::SectorId;
13use subspace_farmer_components::plotting::RecordsEncoder;
14use subspace_farmer_components::sector::SectorContentsMap;
15use subspace_proof_of_space_wgpu::WgpuDevice;
16
17pub struct WgpuRecordsEncoder {
19 devices: Vec<Mutex<WgpuDevice>>,
20 thread_pool: ThreadPool,
21 global_mutex: Arc<AsyncMutex<()>>,
22}
23
24impl fmt::Debug for WgpuRecordsEncoder {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("WgpuRecordsEncoder").finish_non_exhaustive()
27 }
28}
29
30impl GpuRecordsEncoder for WgpuRecordsEncoder {
31 const TYPE: &'static str = "wgpu";
32}
33
34impl RecordsEncoder for WgpuRecordsEncoder {
35 fn encode_records(
36 &mut self,
37 sector_id: &SectorId,
38 records: &mut [Record],
39 abort_early: &AtomicBool,
40 ) -> anyhow::Result<SectorContentsMap> {
41 let pieces_in_sector = records
42 .len()
43 .try_into()
44 .map_err(|error| anyhow::anyhow!("Failed to convert pieces in sector: {error}"))?;
45 let mut sector_contents_map = SectorContentsMap::new(pieces_in_sector);
46
47 {
48 let iter = Mutex::new(
49 (PieceOffset::ZERO..)
50 .zip(records.iter_mut())
51 .zip(sector_contents_map.iter_record_bitfields_mut()),
52 );
53 let plotting_error = Mutex::new(None::<String>);
54
55 self.thread_pool.scope(|scope| {
56 scope.spawn_broadcast(|_scope, _ctx| {
57 let thread_index = current_thread_index().unwrap_or_default();
59 let Some(device) = self.devices.get(thread_index) else {
60 return;
61 };
62 let mut device = device
63 .try_lock()
64 .expect("1:1 mapping between threads and devices; qed");
65
66 loop {
67 self.global_mutex.lock_blocking();
69
70 let Some(((piece_offset, record), mut encoded_chunks_used)) =
73 iter.lock().next()
74 else {
75 return;
76 };
77 let pos_seed = sector_id.derive_evaluation_seed(piece_offset);
78
79 if let Err(error) = device.generate_and_encode_pospace(
80 &pos_seed,
81 record,
82 encoded_chunks_used.iter_mut(),
83 ) {
84 plotting_error.lock().replace(error);
85 return;
86 }
87
88 if abort_early.load(Ordering::Relaxed) {
89 return;
90 }
91 }
92 });
93 });
94
95 let plotting_error = plotting_error.lock().take();
96 if let Some(error) = plotting_error {
97 return Err(anyhow::Error::msg(error));
98 }
99 }
100
101 Ok(sector_contents_map)
102 }
103}
104
105impl WgpuRecordsEncoder {
106 pub fn new(
110 id: u32,
111 devices: Vec<WgpuDevice>,
112 global_mutex: Arc<AsyncMutex<()>>,
113 ) -> Result<Self, ThreadPoolBuildError> {
114 let thread_name = move |thread_index| format!("wgpu-{id:02}.{thread_index:02}");
115 let panic_handler = move |panic_info| {
118 if let Some(index) = current_thread_index() {
119 eprintln!("panic on thread {}: {:?}", thread_name(index), panic_info);
120 } else {
121 eprintln!("rayon panic handler called on non-rayon thread: {panic_info:?}");
123 }
124 exit(1);
125 };
126
127 let thread_pool = ThreadPoolBuilder::new()
128 .thread_name(thread_name)
129 .panic_handler(panic_handler)
130 .num_threads(devices.len())
131 .build()?;
132
133 Ok(Self {
134 devices: devices.into_iter().map(Mutex::new).collect(),
135 thread_pool,
136 global_mutex,
137 })
138 }
139}