1use std::cell::RefCell;
6use std::collections::HashMap;
7
8pub struct ComputePass {
12 pub(super) commands: Vec<(String, usize)>,
14}
15impl ComputePass {
16 pub fn new() -> Self {
18 Self {
19 commands: Vec::new(),
20 }
21 }
22 pub fn dispatch(&mut self, kernel_name: &str, work_size: usize) {
24 self.commands.push((kernel_name.to_string(), work_size));
25 }
26 pub fn num_commands(&self) -> usize {
28 self.commands.len()
29 }
30 pub fn commands(&self) -> &[(String, usize)] {
32 &self.commands
33 }
34 pub fn clear(&mut self) {
36 self.commands.clear();
37 }
38 pub fn total_work_items(&self) -> usize {
40 self.commands.iter().map(|(_, ws)| ws).sum()
41 }
42}
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum BufferUsage {
46 ReadOnly,
48 WriteOnly,
50 ReadWrite,
52 Uniform,
54}
55#[derive(Debug, Clone)]
57pub enum GpuCommand {
58 CopyBuffer {
60 src: BufferId,
62 dst: BufferId,
64 size: usize,
66 },
67 DispatchCompute {
69 kernel_name: String,
71 workgroups: [u32; 3],
73 },
74 Barrier(PipelineBarrier),
76 PushConstant {
78 name: String,
80 value: f64,
82 },
83}
84pub struct ResourceLifecycle {
86 pub(super) events: Vec<ResourceEvent>,
87}
88impl ResourceLifecycle {
89 pub fn new() -> Self {
91 Self { events: Vec::new() }
92 }
93 pub fn record_create(&mut self, id: BufferId, size: usize) {
95 self.events.push(ResourceEvent::Created(id, size));
96 }
97 pub fn record_write(&mut self, id: BufferId) {
99 self.events.push(ResourceEvent::Written(id));
100 }
101 pub fn record_read(&mut self, id: BufferId) {
103 self.events.push(ResourceEvent::Read(id));
104 }
105 pub fn record_destroy(&mut self, id: BufferId) {
107 self.events.push(ResourceEvent::Destroyed(id));
108 }
109 pub fn events(&self) -> &[ResourceEvent] {
111 &self.events
112 }
113 pub fn len(&self) -> usize {
115 self.events.len()
116 }
117 pub fn is_empty(&self) -> bool {
119 self.events.is_empty()
120 }
121 pub fn clear(&mut self) {
123 self.events.clear();
124 }
125 pub fn count_writes(&self, id: BufferId) -> usize {
127 self.events
128 .iter()
129 .filter(|e| matches!(e, ResourceEvent::Written(bid) if * bid == id))
130 .count()
131 }
132 pub fn count_reads(&self, id: BufferId) -> usize {
134 self.events
135 .iter()
136 .filter(|e| matches!(e, ResourceEvent::Read(bid) if * bid == id))
137 .count()
138 }
139}
140#[derive(Debug, Clone, PartialEq, Eq)]
142pub enum PipelineBarrier {
143 StorageReadAfterWrite,
145 UniformReadAfterWrite,
147 Full,
149 None,
151}
152#[derive(Debug, Clone)]
156pub struct OccupancyModel {
157 pub compute_units: u32,
159 pub max_warps_per_cu: u32,
161 pub warp_size: u32,
163 pub shared_mem_per_cu: u32,
165 pub registers_per_cu: u32,
167}
168impl OccupancyModel {
169 pub fn mid_range() -> Self {
171 Self {
172 compute_units: 32,
173 max_warps_per_cu: 32,
174 warp_size: 32,
175 shared_mem_per_cu: 48 * 1024,
176 registers_per_cu: 65536,
177 }
178 }
179 pub fn estimate_occupancy(
186 &self,
187 workgroup_size: u32,
188 shared_mem_bytes: u32,
189 registers_per_thread: u32,
190 ) -> f64 {
191 let warps_per_wg = workgroup_size.div_ceil(self.warp_size);
192 let max_wg_by_warps = self.max_warps_per_cu / warps_per_wg.max(1);
193 let max_wg_by_smem = self
194 .shared_mem_per_cu
195 .checked_div(shared_mem_bytes)
196 .unwrap_or(u32::MAX);
197 let regs_per_wg = registers_per_thread * workgroup_size;
198 let max_wg_by_regs = self
199 .registers_per_cu
200 .checked_div(regs_per_wg)
201 .unwrap_or(u32::MAX);
202 let active_wg = max_wg_by_warps.min(max_wg_by_smem).min(max_wg_by_regs);
203 let active_warps = (active_wg * warps_per_wg).min(self.max_warps_per_cu);
204 (active_warps as f64 / self.max_warps_per_cu as f64).clamp(0.0, 1.0)
205 }
206 pub fn peak_gflops(&self, clock_mhz: f64) -> f64 {
210 let simd_width = self.warp_size as f64;
211 2.0 * simd_width * self.compute_units as f64 * clock_mhz * 1e6 / 1e9
212 }
213}
214pub struct GpuCommandEncoder {
218 pub(super) label: String,
219 pub(super) commands: Vec<GpuCommand>,
220}
221impl GpuCommandEncoder {
222 pub fn new(label: impl Into<String>) -> Self {
224 Self {
225 label: label.into(),
226 commands: Vec::new(),
227 }
228 }
229 pub fn copy_buffer(&mut self, src: BufferId, dst: BufferId, size: usize) {
231 self.commands
232 .push(GpuCommand::CopyBuffer { src, dst, size });
233 }
234 pub fn dispatch_compute(&mut self, kernel_name: &str, workgroups: [u32; 3]) {
236 self.commands.push(GpuCommand::DispatchCompute {
237 kernel_name: kernel_name.to_string(),
238 workgroups,
239 });
240 }
241 pub fn insert_barrier(&mut self, barrier: PipelineBarrier) {
243 self.commands.push(GpuCommand::Barrier(barrier));
244 }
245 pub fn push_constant(&mut self, name: &str, value: f64) {
247 self.commands.push(GpuCommand::PushConstant {
248 name: name.to_string(),
249 value,
250 });
251 }
252 pub fn label(&self) -> &str {
254 &self.label
255 }
256 pub fn command_count(&self) -> usize {
258 self.commands.len()
259 }
260 pub fn commands(&self) -> &[GpuCommand] {
262 &self.commands
263 }
264 pub fn reset(&mut self) {
266 self.commands.clear();
267 }
268 pub fn submit(&self, dispatcher: &mut ComputeDispatcher) -> Result<(), GpuError> {
273 for cmd in &self.commands {
274 if let GpuCommand::CopyBuffer { src, dst, .. } = cmd {
275 dispatcher.copy_buffer(*src, *dst)?;
276 }
277 }
278 Ok(())
279 }
280}
281pub struct ComputeDispatcher {
286 pub(super) buffers: HashMap<BufferId, GpuBuffer>,
287 pub(super) next_id: u32,
288}
289impl ComputeDispatcher {
290 pub fn new() -> Self {
292 Self {
293 buffers: HashMap::new(),
294 next_id: 0,
295 }
296 }
297 pub fn create_buffer(&mut self, size: usize, initial_data: Option<&[f64]>) -> BufferId {
300 let id = BufferId(self.next_id);
301 self.next_id += 1;
302 let buf = match initial_data {
303 Some(data) => {
304 let mut b = GpuBuffer::new(size);
305 let copy_len = data.len().min(size);
306 b.data[..copy_len].copy_from_slice(&data[..copy_len]);
307 b
308 }
309 None => GpuBuffer::new(size),
310 };
311 self.buffers.insert(id, buf);
312 id
313 }
314 pub fn write_buffer(&mut self, id: BufferId, data: &[f64]) -> Result<(), GpuError> {
319 match self.buffers.get_mut(&id) {
320 Some(buf) => {
321 buf.data = data.to_vec();
322 buf.size = data.len();
323 Ok(())
324 }
325 None => Err(GpuError::InvalidBuffer(id)),
326 }
327 }
328 pub fn read_buffer(&self, id: BufferId) -> Result<Vec<f64>, GpuError> {
333 self.buffers
334 .get(&id)
335 .map(|b| b.data.clone())
336 .ok_or(GpuError::InvalidBuffer(id))
337 }
338 pub fn num_buffers(&self) -> usize {
340 self.buffers.len()
341 }
342 pub fn has_buffer(&self, id: BufferId) -> bool {
344 self.buffers.contains_key(&id)
345 }
346 pub fn buffer_size(&self, id: BufferId) -> Result<usize, GpuError> {
348 self.buffers
349 .get(&id)
350 .map(|b| b.size)
351 .ok_or(GpuError::InvalidBuffer(id))
352 }
353 pub fn destroy_buffer(&mut self, id: BufferId) -> Result<(), GpuError> {
355 self.buffers
356 .remove(&id)
357 .map(|_| ())
358 .ok_or(GpuError::InvalidBuffer(id))
359 }
360 pub fn copy_buffer(&mut self, src: BufferId, dst: BufferId) -> Result<(), GpuError> {
362 let src_data = self
363 .buffers
364 .get(&src)
365 .ok_or(GpuError::InvalidBuffer(src))?
366 .data
367 .clone();
368 let dst_buf = self
369 .buffers
370 .get_mut(&dst)
371 .ok_or(GpuError::InvalidBuffer(dst))?;
372 if src_data.len() != dst_buf.size {
373 return Err(GpuError::SizeMismatch {
374 expected: dst_buf.size,
375 got: src_data.len(),
376 });
377 }
378 dst_buf.data = src_data;
379 Ok(())
380 }
381 pub fn dispatch_map(
386 &mut self,
387 buf_in: BufferId,
388 buf_out: BufferId,
389 f: impl Fn(f64) -> f64,
390 ) -> Result<(), GpuError> {
391 let input = self
392 .buffers
393 .get(&buf_in)
394 .ok_or(GpuError::InvalidBuffer(buf_in))?
395 .data
396 .clone();
397 let out_buf = self
398 .buffers
399 .get_mut(&buf_out)
400 .ok_or(GpuError::InvalidBuffer(buf_out))?;
401 if input.len() != out_buf.size {
402 return Err(GpuError::SizeMismatch {
403 expected: out_buf.size,
404 got: input.len(),
405 });
406 }
407 out_buf.data = input.iter().map(|&x| f(x)).collect();
408 Ok(())
409 }
410 pub fn dispatch_map_indexed(
412 &mut self,
413 buf_in: BufferId,
414 buf_out: BufferId,
415 f: impl Fn(usize, f64) -> f64,
416 ) -> Result<(), GpuError> {
417 let input = self
418 .buffers
419 .get(&buf_in)
420 .ok_or(GpuError::InvalidBuffer(buf_in))?
421 .data
422 .clone();
423 let out_buf = self
424 .buffers
425 .get_mut(&buf_out)
426 .ok_or(GpuError::InvalidBuffer(buf_out))?;
427 if input.len() != out_buf.size {
428 return Err(GpuError::SizeMismatch {
429 expected: out_buf.size,
430 got: input.len(),
431 });
432 }
433 out_buf.data = input.iter().enumerate().map(|(i, &x)| f(i, x)).collect();
434 Ok(())
435 }
436 pub fn dispatch_zip_map(
438 &mut self,
439 buf_a: BufferId,
440 buf_b: BufferId,
441 buf_out: BufferId,
442 f: impl Fn(f64, f64) -> f64,
443 ) -> Result<(), GpuError> {
444 let a_data = self
445 .buffers
446 .get(&buf_a)
447 .ok_or(GpuError::InvalidBuffer(buf_a))?
448 .data
449 .clone();
450 let b_data = self
451 .buffers
452 .get(&buf_b)
453 .ok_or(GpuError::InvalidBuffer(buf_b))?
454 .data
455 .clone();
456 if a_data.len() != b_data.len() {
457 return Err(GpuError::SizeMismatch {
458 expected: a_data.len(),
459 got: b_data.len(),
460 });
461 }
462 let out_buf = self
463 .buffers
464 .get_mut(&buf_out)
465 .ok_or(GpuError::InvalidBuffer(buf_out))?;
466 if a_data.len() != out_buf.size {
467 return Err(GpuError::SizeMismatch {
468 expected: out_buf.size,
469 got: a_data.len(),
470 });
471 }
472 out_buf.data = a_data
473 .iter()
474 .zip(b_data.iter())
475 .map(|(&a, &b)| f(a, b))
476 .collect();
477 Ok(())
478 }
479 pub fn dispatch_reduce(
486 &self,
487 buf: BufferId,
488 f: impl Fn(f64, f64) -> f64,
489 ) -> Result<f64, GpuError> {
490 let data = self.buffers.get(&buf).ok_or(GpuError::InvalidBuffer(buf))?;
491 let mut iter = data.data.iter().copied();
492 let first = iter.next().ok_or(GpuError::EmptyBuffer)?;
493 Ok(iter.fold(first, f))
494 }
495 pub fn dispatch_sph_density(
513 &mut self,
514 pos_buf: BufferId,
515 mass_buf: BufferId,
516 h: f64,
517 out_density_buf: BufferId,
518 ) -> Result<(), GpuError> {
519 let positions = self
520 .buffers
521 .get(&pos_buf)
522 .ok_or(GpuError::InvalidBuffer(pos_buf))?
523 .data
524 .clone();
525 let masses = self
526 .buffers
527 .get(&mass_buf)
528 .ok_or(GpuError::InvalidBuffer(mass_buf))?
529 .data
530 .clone();
531 let n = positions.len() / 3;
532 let h2 = h * h;
533 let mut densities = vec![0.0f64; n];
534 for i in 0..n {
535 let xi = positions[i * 3];
536 let yi = positions[i * 3 + 1];
537 let zi = positions[i * 3 + 2];
538 let mut rho = 0.0;
539 for j in 0..n {
540 let dx = xi - positions[j * 3];
541 let dy = yi - positions[j * 3 + 1];
542 let dz = zi - positions[j * 3 + 2];
543 let r2 = dx * dx + dy * dy + dz * dz;
544 if r2 < h2 {
545 let q = 1.0 - r2 / h2;
546 rho += masses[j] * q * q;
547 }
548 }
549 densities[i] = rho;
550 }
551 let out_buf = self
552 .buffers
553 .get_mut(&out_density_buf)
554 .ok_or(GpuError::InvalidBuffer(out_density_buf))?;
555 out_buf.data = densities;
556 out_buf.size = n;
557 Ok(())
558 }
559 pub fn dispatch_reduction_tree(&self, buf: BufferId) -> Result<f64, GpuError> {
566 let data = self
567 .buffers
568 .get(&buf)
569 .ok_or(GpuError::InvalidBuffer(buf))?
570 .data
571 .clone();
572 if data.is_empty() {
573 return Ok(0.0);
574 }
575 let mut work = data;
576 let mut len = work.len();
577 while len > 1 {
578 let half = len / 2;
579 for i in 0..half {
580 work[i] = work[i * 2] + work[i * 2 + 1];
581 }
582 if len % 2 == 1 {
583 work[half] = work[len - 1];
584 len = half + 1;
585 } else {
586 len = half;
587 }
588 }
589 Ok(work[0])
590 }
591 pub fn dispatch_inclusive_scan(
597 &mut self,
598 buf_in: BufferId,
599 buf_out: BufferId,
600 ) -> Result<(), GpuError> {
601 let data = self
602 .buffers
603 .get(&buf_in)
604 .ok_or(GpuError::InvalidBuffer(buf_in))?
605 .data
606 .clone();
607 let n = data.len();
608 let mut result = data;
609 for i in 1..n {
610 result[i] += result[i - 1];
611 }
612 let out = self
613 .buffers
614 .get_mut(&buf_out)
615 .ok_or(GpuError::InvalidBuffer(buf_out))?;
616 out.data = result;
617 out.size = n;
618 Ok(())
619 }
620 pub fn dispatch_radix_sort(&self, buf: BufferId) -> Result<Vec<f64>, GpuError> {
627 let data = self
628 .buffers
629 .get(&buf)
630 .ok_or(GpuError::InvalidBuffer(buf))?
631 .data
632 .clone();
633 let n = data.len();
634 if n == 0 {
635 return Ok(Vec::new());
636 }
637 let mut keys: Vec<u64> = data.iter().map(|&v| v.to_bits()).collect();
638 for pass in 0..32usize {
639 let shift = pass * 2;
640 let mut counts = [0usize; 4];
641 for &k in &keys {
642 counts[((k >> shift) & 0x3) as usize] += 1;
643 }
644 let mut starts = [0usize; 4];
645 for i in 1..4 {
646 starts[i] = starts[i - 1] + counts[i - 1];
647 }
648 let mut out = vec![0u64; n];
649 let mut pos = starts;
650 for &k in &keys {
651 let digit = ((k >> shift) & 0x3) as usize;
652 out[pos[digit]] = k;
653 pos[digit] += 1;
654 }
655 keys = out;
656 }
657 Ok(keys.iter().map(|&bits| f64::from_bits(bits)).collect())
658 }
659}
660#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
662pub struct BufferHandle(pub usize);
663#[derive(Debug, Clone)]
665pub struct KernelSpec {
666 pub name: String,
668 pub workgroup_size: [u32; 3],
670 pub buffer_bindings: Vec<BufferId>,
672}
673impl KernelSpec {
674 pub fn new(name: impl Into<String>, workgroup_x: u32, buffer_bindings: Vec<BufferId>) -> Self {
676 Self {
677 name: name.into(),
678 workgroup_size: [workgroup_x, 1, 1],
679 buffer_bindings,
680 }
681 }
682 pub fn with_workgroup_3d(
684 name: impl Into<String>,
685 workgroup_size: [u32; 3],
686 buffer_bindings: Vec<BufferId>,
687 ) -> Self {
688 Self {
689 name: name.into(),
690 workgroup_size,
691 buffer_bindings,
692 }
693 }
694 pub fn num_workgroups_x(&self, total_items: u32) -> u32 {
696 total_items.div_ceil(self.workgroup_size[0])
697 }
698 pub fn threads_per_workgroup(&self) -> u32 {
700 self.workgroup_size[0] * self.workgroup_size[1] * self.workgroup_size[2]
701 }
702}
703#[derive(Debug, Clone)]
705pub struct GpuBuffer {
706 pub data: Vec<f64>,
708 pub size: usize,
711}
712impl GpuBuffer {
713 pub fn new(size: usize) -> Self {
715 Self {
716 data: vec![0.0; size],
717 size,
718 }
719 }
720 pub fn from_data(initial_data: Vec<f64>) -> Self {
722 let size = initial_data.len();
723 Self {
724 data: initial_data,
725 size,
726 }
727 }
728 pub fn fill(&mut self, value: f64) {
730 for v in &mut self.data {
731 *v = value;
732 }
733 }
734 pub fn clear(&mut self) {
736 self.fill(0.0);
737 }
738 pub fn as_slice(&self) -> &[f64] {
740 &self.data
741 }
742 pub fn as_mut_slice(&mut self) -> &mut [f64] {
744 &mut self.data
745 }
746 pub fn byte_size(&self) -> usize {
748 self.size * std::mem::size_of::<f64>()
749 }
750}
751#[derive(Debug, Clone, PartialEq)]
753pub enum GpuError {
754 InvalidBuffer(BufferId),
756 SizeMismatch {
758 expected: usize,
760 got: usize,
762 },
763 EmptyBuffer,
765 NotFound(String),
767}
768pub struct CpuBackend {
772 pub(super) buffers: RefCell<Vec<Vec<f64>>>,
773}
774impl CpuBackend {
775 pub fn new() -> Self {
777 Self {
778 buffers: RefCell::new(Vec::new()),
779 }
780 }
781 pub fn num_buffers(&self) -> usize {
783 self.buffers.borrow().len()
784 }
785 pub fn total_elements(&self) -> usize {
787 self.buffers.borrow().iter().map(|b| b.len()).sum()
788 }
789}
790#[derive(Debug, Clone)]
792pub enum ResourceEvent {
793 Created(BufferId, usize),
795 Written(BufferId),
797 Read(BufferId),
799 Destroyed(BufferId),
801}
802#[derive(Debug, Clone, Default)]
804pub struct WarpDivergenceRecord {
805 pub total_branches: u64,
807 pub divergent_branches: u64,
809}
810impl WarpDivergenceRecord {
811 pub fn divergence_rate(&self) -> f64 {
813 if self.total_branches == 0 {
814 0.0
815 } else {
816 self.divergent_branches as f64 / self.total_branches as f64
817 }
818 }
819 pub fn performance_penalty(&self, warp_size: u32) -> f64 {
823 let rate = self.divergence_rate();
824 1.0 + rate * (warp_size as f64 - 1.0) / warp_size as f64
825 }
826}
827pub struct TimelineSemaphore {
833 pub value: u64,
835 pub(super) signal_history: Vec<u64>,
837 pub(super) wait_history: Vec<u64>,
839}
840impl TimelineSemaphore {
841 pub fn new() -> Self {
843 Self {
844 value: 0,
845 signal_history: Vec::new(),
846 wait_history: Vec::new(),
847 }
848 }
849 pub fn signal(&mut self, new_value: u64) {
851 assert!(
852 new_value > self.value,
853 "semaphore values must increase monotonically"
854 );
855 self.value = new_value;
856 self.signal_history.push(new_value);
857 }
858 pub fn wait(&mut self, wait_value: u64) -> bool {
862 self.wait_history.push(wait_value);
863 self.value >= wait_value
864 }
865 pub fn current_value(&self) -> u64 {
867 self.value
868 }
869 pub fn signal_count(&self) -> usize {
871 self.signal_history.len()
872 }
873}
874#[derive(Debug, Clone)]
878pub struct MemoryBandwidthModel {
879 pub peak_bandwidth_gbs: f64,
881 pub peak_compute_gflops: f64,
883}
884impl MemoryBandwidthModel {
885 pub fn mid_range() -> Self {
887 Self {
888 peak_bandwidth_gbs: 480.0,
889 peak_compute_gflops: 10000.0,
890 }
891 }
892 pub fn arithmetic_intensity(flops: f64, bytes_accessed: f64) -> f64 {
897 if bytes_accessed < 1e-30 {
898 f64::INFINITY
899 } else {
900 flops / bytes_accessed
901 }
902 }
903 pub fn roofline_performance(&self, arithmetic_intensity: f64) -> f64 {
905 let bw_bound = arithmetic_intensity * self.peak_bandwidth_gbs;
906 bw_bound.min(self.peak_compute_gflops)
907 }
908 pub fn estimated_runtime_ms(&self, flops: f64, bytes_accessed: f64) -> f64 {
913 let intensity = Self::arithmetic_intensity(flops, bytes_accessed);
914 let perf_gflops = self.roofline_performance(intensity);
915 if perf_gflops < 1e-30 {
916 return f64::INFINITY;
917 }
918 (flops / (perf_gflops * 1e9)) * 1e3
919 }
920 pub fn is_bandwidth_bound(&self, arithmetic_intensity: f64) -> bool {
924 let ridge_point = self.peak_compute_gflops / self.peak_bandwidth_gbs;
925 arithmetic_intensity < ridge_point
926 }
927}
928#[derive(Debug, Clone, Copy)]
930pub struct BufferBinding {
931 pub binding: u32,
933 pub buffer_id: BufferId,
935 pub usage: BufferUsage,
937}
938impl BufferBinding {
939 pub fn new(binding: u32, buffer_id: BufferId, usage: BufferUsage) -> Self {
941 Self {
942 binding,
943 buffer_id,
944 usage,
945 }
946 }
947 pub fn read(binding: u32, buffer_id: BufferId) -> Self {
949 Self::new(binding, buffer_id, BufferUsage::ReadOnly)
950 }
951 pub fn write(binding: u32, buffer_id: BufferId) -> Self {
953 Self::new(binding, buffer_id, BufferUsage::WriteOnly)
954 }
955 pub fn read_write(binding: u32, buffer_id: BufferId) -> Self {
957 Self::new(binding, buffer_id, BufferUsage::ReadWrite)
958 }
959 pub fn uniform(binding: u32, buffer_id: BufferId) -> Self {
961 Self::new(binding, buffer_id, BufferUsage::Uniform)
962 }
963}
964#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
966pub struct BufferId(pub u32);