1use std::borrow::Cow;
10
11pub type Result<T> = std::result::Result<T, String>;
12
13mod kernels;
14pub use kernels::cpu; pub mod demo; pub struct Context {
19 pub device: wgpu::Device,
20 pub queue: wgpu::Queue,
21 pub backend: wgpu::Backend,
22 pub adapter_name: String,
23}
24
25pub struct Tensor {
28 pub buf: wgpu::Buffer,
29 pub shape: Vec<usize>,
30}
31impl Tensor {
32 pub fn len(&self) -> usize { self.shape.iter().product() }
33 pub fn is_empty(&self) -> bool { self.len() == 0 }
34}
35
36impl Context {
37 pub async fn new() -> Result<Self> {
39 let instance = wgpu::Instance::default();
40 let adapter = instance
41 .request_adapter(&wgpu::RequestAdapterOptions {
42 power_preference: wgpu::PowerPreference::HighPerformance,
43 ..Default::default()
44 })
45 .await
46 .map_err(|e| format!("no compute adapter: {e:?}"))?;
47 let info = adapter.get_info();
48 let (device, queue) = adapter
49 .request_device(&wgpu::DeviceDescriptor {
50 label: Some("ferric"),
51 required_features: wgpu::Features::empty(),
52 required_limits: wgpu::Limits::downlevel_defaults(),
54 memory_hints: wgpu::MemoryHints::Performance,
55 ..Default::default()
56 })
57 .await
58 .map_err(|e| format!("no compute device: {e:?}"))?;
59 Ok(Self { device, queue, backend: info.backend, adapter_name: info.name })
60 }
61
62 #[cfg(not(target_arch = "wasm32"))]
65 pub async fn enumerate() -> Vec<(String, wgpu::Backend, wgpu::DeviceType)> {
66 let instance = wgpu::Instance::default();
67 instance.enumerate_adapters(wgpu::Backends::all()).await.iter()
68 .map(|a| { let i = a.get_info(); (i.name, i.backend, i.device_type) })
69 .collect()
70 }
71
72 #[cfg(not(target_arch = "wasm32"))]
75 pub async fn for_adapter(idx: usize) -> Result<Self> {
76 let instance = wgpu::Instance::default();
77 let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
78 let adapter = adapters.into_iter().nth(idx).ok_or_else(|| format!("no adapter at index {idx}"))?;
79 let info = adapter.get_info();
80 let (device, queue) = adapter
81 .request_device(&wgpu::DeviceDescriptor {
82 label: Some("ferric"),
83 required_features: wgpu::Features::empty(),
84 required_limits: wgpu::Limits::downlevel_defaults(),
85 memory_hints: wgpu::MemoryHints::Performance,
86 ..Default::default()
87 })
88 .await
89 .map_err(|e| format!("no compute device: {e:?}"))?;
90 Ok(Self { device, queue, backend: info.backend, adapter_name: info.name })
91 }
92
93 pub(crate) fn storage(&self, label: &str, data: &[f32]) -> wgpu::Buffer {
94 use wgpu::util::DeviceExt;
95 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
96 label: Some(label),
97 contents: bytemuck::cast_slice(data),
98 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
99 })
100 }
101 pub(crate) fn storage_u32(&self, label: &str, data: &[u32]) -> wgpu::Buffer {
102 use wgpu::util::DeviceExt;
103 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
104 label: Some(label),
105 contents: bytemuck::cast_slice(data),
106 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
107 })
108 }
109 pub(crate) fn copy_buf(&self, src: &wgpu::Buffer, len: usize) -> wgpu::Buffer {
111 let dst = self.device.create_buffer(&wgpu::BufferDescriptor {
112 label: Some("dup"),
113 size: (len * 4) as u64,
114 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
115 mapped_at_creation: false,
116 });
117 let mut enc = self.device.create_command_encoder(&Default::default());
118 enc.copy_buffer_to_buffer(src, 0, &dst, 0, (len * 4) as u64);
119 self.queue.submit([enc.finish()]);
120 dst
121 }
122 pub(crate) fn uniform_u32(&self, label: &str, data: &[u32]) -> wgpu::Buffer {
123 use wgpu::util::DeviceExt;
124 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
125 label: Some(label),
126 contents: bytemuck::cast_slice(data),
127 usage: wgpu::BufferUsages::UNIFORM,
128 })
129 }
130 pub(crate) fn out_buffer(&self, len: usize) -> wgpu::Buffer {
132 self.device.create_buffer(&wgpu::BufferDescriptor {
133 label: Some("out"),
134 size: (len * 4) as u64,
135 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
136 mapped_at_creation: false,
137 })
138 }
139 pub(crate) fn pipeline(&self, label: &str, wgsl: &str) -> wgpu::ComputePipeline {
141 let module = self.device.create_shader_module(wgpu::ShaderModuleDescriptor {
142 label: Some(label),
143 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl.to_string())),
144 });
145 self.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
146 label: Some(label),
147 layout: None,
148 module: &module,
149 entry_point: Some("main"),
150 compilation_options: Default::default(),
151 cache: None,
152 })
153 }
154 pub(crate) fn dispatch(&self, pipeline: &wgpu::ComputePipeline, binds: &[&wgpu::Buffer], groups: (u32, u32, u32)) {
156 let entries: Vec<wgpu::BindGroupEntry> = binds.iter().enumerate()
157 .map(|(i, b)| wgpu::BindGroupEntry { binding: i as u32, resource: b.as_entire_binding() })
158 .collect();
159 let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
160 label: Some("bg"),
161 layout: &pipeline.get_bind_group_layout(0),
162 entries: &entries,
163 });
164 let mut enc = self.device.create_command_encoder(&Default::default());
165 {
166 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None });
167 pass.set_pipeline(pipeline);
168 pass.set_bind_group(0, &bg, &[]);
169 pass.dispatch_workgroups(groups.0, groups.1, groups.2);
170 }
171 self.queue.submit([enc.finish()]);
172 }
173 pub(crate) async fn readback(&self, buf: &wgpu::Buffer, len: usize) -> Result<Vec<f32>> {
175 let bytes = (len * 4) as u64;
176 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
177 label: Some("staging"),
178 size: bytes,
179 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
180 mapped_at_creation: false,
181 });
182 let mut enc = self.device.create_command_encoder(&Default::default());
183 enc.copy_buffer_to_buffer(buf, 0, &staging, 0, bytes);
184 self.queue.submit([enc.finish()]);
185 let (tx, rx) = flume::bounded(1);
186 staging.slice(..).map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
187 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
188 rx.recv_async().await.map_err(|e| format!("recv: {e:?}"))?.map_err(|e| format!("map: {e:?}"))?;
189 let data = staging.slice(..).get_mapped_range().map_err(|e| format!("map range: {e:?}"))?;
190 let out: Vec<f32> = bytemuck::cast_slice(&data).to_vec();
191 drop(data);
192 staging.unmap();
193 Ok(out)
194 }
195
196 pub async fn matmul(&self, a: &[f32], b: &[f32], m: u32, k: u32, n: u32) -> Result<Vec<f32>> {
198 assert_eq!(a.len(), (m * k) as usize);
199 assert_eq!(b.len(), (k * n) as usize);
200 let out_len = (m * n) as usize;
201 let out_bytes = (out_len * 4) as u64;
202
203 let a_buf = self.storage("a", a);
204 let b_buf = self.storage("b", b);
205 let dims_buf = self.uniform_u32("dims", &[m, k, n, 0]);
206 let out_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
207 label: Some("out"),
208 size: out_bytes,
209 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
210 mapped_at_creation: false,
211 });
212
213 let shader = self.device.create_shader_module(wgpu::ShaderModuleDescriptor {
214 label: Some("matmul"),
215 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(MATMUL_WGSL)),
216 });
217 let pipeline = self.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
218 label: Some("matmul"),
219 layout: None,
220 module: &shader,
221 entry_point: Some("main"),
222 compilation_options: Default::default(),
223 cache: None,
224 });
225 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
226 label: Some("matmul-bg"),
227 layout: &pipeline.get_bind_group_layout(0),
228 entries: &[
229 wgpu::BindGroupEntry { binding: 0, resource: a_buf.as_entire_binding() },
230 wgpu::BindGroupEntry { binding: 1, resource: b_buf.as_entire_binding() },
231 wgpu::BindGroupEntry { binding: 2, resource: out_buf.as_entire_binding() },
232 wgpu::BindGroupEntry { binding: 3, resource: dims_buf.as_entire_binding() },
233 ],
234 });
235
236 let mut enc = self.device.create_command_encoder(&Default::default());
237 {
238 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
239 label: Some("matmul"),
240 timestamp_writes: None,
241 });
242 pass.set_pipeline(&pipeline);
243 pass.set_bind_group(0, &bind_group, &[]);
244 let gx = (m + 15) / 16;
245 let gy = (n + 15) / 16;
246 pass.dispatch_workgroups(gx, gy, 1);
247 }
248 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
250 label: Some("staging"),
251 size: out_bytes,
252 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
253 mapped_at_creation: false,
254 });
255 enc.copy_buffer_to_buffer(&out_buf, 0, &staging, 0, out_bytes);
256 self.queue.submit([enc.finish()]);
257
258 let (tx, rx) = flume::bounded(1);
260 staging.slice(..).map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
261 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
262 rx.recv_async().await.map_err(|e| format!("recv: {e:?}"))?.map_err(|e| format!("map: {e:?}"))?;
263 let data = staging.slice(..).get_mapped_range().map_err(|e| format!("map range: {e:?}"))?;
264 let out: Vec<f32> = bytemuck::cast_slice(&data).to_vec();
265 drop(data);
266 staging.unmap();
267 Ok(out)
268 }
269}
270
271pub(crate) const MATMUL_WGSL: &str = r#"
272@group(0) @binding(0) var<storage, read> a: array<f32>;
273@group(0) @binding(1) var<storage, read> b: array<f32>;
274@group(0) @binding(2) var<storage, read_write> out: array<f32>;
275@group(0) @binding(3) var<uniform> dims: vec4<u32>; // m, k, n, _
276
277@compute @workgroup_size(16, 16, 1)
278fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
279 let m = dims.x; let k = dims.y; let n = dims.z;
280 let row = gid.x; let col = gid.y;
281 if (row >= m || col >= n) { return; }
282 var acc: f32 = 0.0;
283 for (var i: u32 = 0u; i < k; i = i + 1u) {
284 acc = acc + a[row * k + i] * b[i * n + col];
285 }
286 out[row * n + col] = acc;
287}
288"#;
289
290pub fn matmul_cpu(a: &[f32], b: &[f32], m: usize, k: usize, n: usize) -> Vec<f32> {
292 let mut c = vec![0.0f32; m * n];
293 for row in 0..m {
294 for col in 0..n {
295 let mut acc = 0.0f32;
296 for i in 0..k {
297 acc += a[row * k + i] * b[i * n + col];
298 }
299 c[row * n + col] = acc;
300 }
301 }
302 c
303}
304
305pub fn max_abs_diff(x: &[f32], y: &[f32]) -> f32 {
307 x.iter().zip(y).map(|(a, b)| (a - b).abs()).fold(0.0, f32::max)
308}