sim_lib_compute_wgpu/
storage.rs1use std::{
4 any::Any,
5 sync::{Arc, OnceLock},
6};
7
8use sim_kernel::{DefaultFactory, Error, Factory, Result, Symbol, Value};
9use sim_lib_numbers_tensor::{TensorLocation, TensorStorage};
10
11use crate::{
12 WgpuArenaAllocation, WgpuMaterializationCache, WgpuPhysicalCounters, WgpuResidentSegment,
13 dispatch::{read_f32s, readback_buffer},
14 site::WgpuExecutionContext,
15};
16
17pub struct WgpuResidentStorage {
19 site: Symbol,
20 allocation: WgpuArenaAllocation,
21 pipeline: Symbol,
22 segments: Arc<[WgpuResidentSegment]>,
23 dtype: Symbol,
24 len: usize,
25 buffer: Arc<wgpu::Buffer>,
26 context: WgpuExecutionContext,
27 counters: WgpuPhysicalCounters,
28 cache: WgpuMaterializationCache,
29 materialized: OnceLock<Result<Arc<dyn TensorStorage>>>,
30}
31
32pub(crate) struct WgpuResidentStorageDescriptor {
33 pub(crate) site: Symbol,
34 pub(crate) allocation: WgpuArenaAllocation,
35 pub(crate) pipeline: Symbol,
36 pub(crate) segments: Vec<WgpuResidentSegment>,
37 pub(crate) dtype: Symbol,
38 pub(crate) len: usize,
39 pub(crate) buffer: Arc<wgpu::Buffer>,
40 pub(crate) context: WgpuExecutionContext,
41 pub(crate) counters: WgpuPhysicalCounters,
42}
43
44impl WgpuResidentStorage {
45 pub(crate) fn new(descriptor: WgpuResidentStorageDescriptor) -> Self {
47 Self {
48 site: descriptor.site,
49 allocation: descriptor.allocation,
50 pipeline: descriptor.pipeline,
51 segments: descriptor.segments.into(),
52 dtype: descriptor.dtype,
53 len: descriptor.len,
54 buffer: descriptor.buffer,
55 context: descriptor.context,
56 counters: descriptor.counters,
57 cache: WgpuMaterializationCache::default(),
58 materialized: OnceLock::new(),
59 }
60 }
61
62 pub fn allocation(&self) -> &WgpuArenaAllocation {
64 &self.allocation
65 }
66
67 pub fn pipeline(&self) -> &Symbol {
69 &self.pipeline
70 }
71
72 pub fn segments(&self) -> &[WgpuResidentSegment] {
74 &self.segments
75 }
76
77 pub(crate) fn buffer(&self) -> Arc<wgpu::Buffer> {
79 self.buffer.clone()
80 }
81
82 pub(crate) fn context(&self) -> &WgpuExecutionContext {
84 &self.context
85 }
86}
87
88impl TensorStorage for WgpuResidentStorage {
89 fn dtype(&self) -> &Symbol {
90 &self.dtype
91 }
92
93 fn len(&self) -> usize {
94 self.len
95 }
96
97 fn location(&self) -> TensorLocation {
98 TensorLocation::Resident {
99 site: self.site.clone(),
100 allocation: self.allocation.id_symbol(),
101 }
102 }
103
104 fn cell(&self, index: usize) -> Result<Value> {
105 self.materialize()?.cell(index)
106 }
107
108 fn materialize(&self) -> Result<Arc<dyn TensorStorage>> {
109 self.materialized
110 .get_or_init(|| {
111 self.counters.record_full_readback();
112 let size = self.allocation.bytes.max(4);
113 let readback = readback_buffer(&self.context.device, size, "materialize");
114 let mut encoder =
115 self.context
116 .device
117 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
118 label: Some("sim-compute-wgpu-materialize-encoder"),
119 });
120 encoder.copy_buffer_to_buffer(&self.buffer, 0, &readback, 0, size);
121 self.context.queue.submit([encoder.finish()]);
122 self.counters.record_submit(&self.segments);
123 let values = self
124 .cache
125 .get_or_try_init(|| {
126 let values = read_f32s(&self.context, &readback, self.len)
127 .map_err(|err| err.to_string())?;
128 Ok(values
129 .iter()
130 .flat_map(|value| value.to_ne_bytes())
131 .collect::<Vec<_>>()
132 .into())
133 })
134 .map_err(Error::Eval)?;
135 let cells = values
136 .chunks_exact(4)
137 .take(self.len)
138 .map(|bytes| {
139 let value = f32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
140 DefaultFactory.number_literal(self.dtype.clone(), value.to_string())
141 })
142 .collect::<Result<Vec<_>>>()?;
143 Ok(Arc::new(BoxedWgpuStorage::new(
144 self.dtype.clone(),
145 cells.into(),
146 )))
147 })
148 .clone()
149 }
150
151 fn as_any(&self) -> &dyn Any {
152 self
153 }
154}
155
156struct BoxedWgpuStorage {
157 dtype: Symbol,
158 cells: Arc<[Value]>,
159}
160
161impl BoxedWgpuStorage {
162 fn new(dtype: Symbol, cells: Arc<[Value]>) -> Self {
163 Self { dtype, cells }
164 }
165}
166
167impl TensorStorage for BoxedWgpuStorage {
168 fn dtype(&self) -> &Symbol {
169 &self.dtype
170 }
171
172 fn len(&self) -> usize {
173 self.cells.len()
174 }
175
176 fn location(&self) -> TensorLocation {
177 TensorLocation::Host
178 }
179
180 fn cell(&self, index: usize) -> Result<Value> {
181 self.cells
182 .get(index)
183 .cloned()
184 .ok_or_else(|| Error::Eval("wgpu tensor cell index was out of bounds".to_owned()))
185 }
186
187 fn materialize(&self) -> Result<Arc<dyn TensorStorage>> {
188 Ok(Arc::new(Self {
189 dtype: self.dtype.clone(),
190 cells: self.cells.clone(),
191 }))
192 }
193
194 fn as_any(&self) -> &dyn Any {
195 self
196 }
197}
198
199impl WgpuArenaAllocation {
200 fn id_symbol(&self) -> Symbol {
201 Symbol::qualified("compute.alloc.wgpu", format!("{:?}", self.id))
202 }
203}
204
205impl sim_kernel::Object for WgpuArenaAllocation {
206 fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
207 Ok(format!("#<wgpu-allocation {:?}>", self.id))
208 }
209
210 fn as_any(&self) -> &dyn Any {
211 self
212 }
213}
214
215impl sim_kernel::ObjectCompat for WgpuArenaAllocation {
216 fn class(&self, _cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
217 DefaultFactory.class_stub(
218 sim_kernel::CORE_FUNCTION_CLASS_ID,
219 Symbol::qualified("compute", "WgpuAllocation"),
220 )
221 }
222}