1pub use crate::capability::{
5 Backend, CapabilityFacts, CapabilityReport, ComputeCapability, ExecutionPolicy, FallbackReason,
6 FallbackRecord, KernelAvailability,
7};
8use crate::capability::{apply_failure_to_facts, evaluate_capabilities};
9pub use crate::error::{GpuError, GpuResult};
10
11pub(crate) fn host_facts() -> CapabilityFacts {
12 CapabilityFacts::not_built()
13}
14
15#[derive(Debug)]
16pub struct GpuContext;
17impl GpuContext {
18 pub fn init() -> GpuResult<Self> {
19 Err(GpuError::NoGpu)
20 }
21 pub fn is_available() -> bool {
22 false
23 }
24
25 pub fn compute_capability(&self) -> Option<ComputeCapability> {
27 None
28 }
29}
30
31#[derive(Debug)]
32pub struct KernelModule;
33#[derive(Debug)]
34pub struct Function;
35impl KernelModule {
36 pub fn load() -> GpuResult<Self> {
37 Err(GpuError::NoGpu)
38 }
39 pub fn load_satsolver() -> GpuResult<Self> {
40 Err(GpuError::NoGpu)
41 }
42 pub fn get_function(&self, _: &str) -> GpuResult<Function> {
43 Err(GpuError::NoGpu)
44 }
45}
46
47pub struct GpuBuffer<T> {
48 data: Vec<T>,
49}
50impl<T: Default + Clone> GpuBuffer<T> {
51 pub fn alloc(len: usize) -> GpuResult<Self> {
64 crate::error::checked_alloc_bytes::<T>(len)?;
65 let mut data: Vec<T> = Vec::new();
66 data.try_reserve_exact(len)
67 .map_err(|e| GpuError::MemoryError(format!("alloc({len}): reserve failed: {e:?}")))?;
68 data.resize(len, T::default());
69 Ok(Self { data })
70 }
71 pub fn from_slice(data: &[T]) -> GpuResult<Self> {
74 Ok(Self {
75 data: data.to_vec(),
76 })
77 }
78 pub fn to_vec(&self) -> GpuResult<Vec<T>> {
80 Ok(self.data.clone())
81 }
82 pub fn upload(&mut self, data: &[T]) -> GpuResult<()> {
89 if data.len() != self.data.len() {
90 return Err(GpuError::MemoryError(format!(
91 "upload: length mismatch, buffer has {} elements but input has {}",
92 self.data.len(),
93 data.len()
94 )));
95 }
96 self.data.clone_from_slice(data);
97 Ok(())
98 }
99 pub fn len(&self) -> usize {
100 self.data.len()
101 }
102 pub fn is_empty(&self) -> bool {
103 self.data.is_empty()
104 }
105 pub fn as_device_ptr(&self) -> *const T {
106 self.data.as_ptr()
107 }
108}
109
110impl GpuBuffer<f32> {
111 pub fn zero_prefix(&mut self, count: usize) -> GpuResult<()> {
116 if count > self.data.len() {
117 return Err(GpuError::MemoryError(format!(
118 "zero_prefix: count {count} > buffer len {}",
119 self.data.len()
120 )));
121 }
122 for slot in &mut self.data[..count] {
123 *slot = 0.0;
124 }
125 Ok(())
126 }
127}
128
129pub struct GpuAccelerator {
130 capabilities: CapabilityReport,
131}
132
133impl GpuAccelerator {
134 pub fn new() -> Self {
136 match Self::with_policy(ExecutionPolicy::PreferGpu) {
137 Ok(acc) => acc,
138 Err(_) => unreachable!("PreferGpu construction is infallible"),
139 }
140 }
141
142 pub fn require_gpu() -> GpuResult<Self> {
144 Self::with_policy(ExecutionPolicy::RequireGpu)
145 }
146
147 pub fn with_policy(policy: ExecutionPolicy) -> GpuResult<Self> {
149 let mut facts = host_facts();
150 apply_failure_to_facts(&mut facts, FallbackReason::CudaFeatureNotBuilt);
151 let capabilities = evaluate_capabilities(&facts);
152 match policy {
153 ExecutionPolicy::PreferGpu => Ok(Self { capabilities }),
154 ExecutionPolicy::RequireGpu => {
155 let fb = capabilities.fallback.clone().unwrap_or_else(|| {
156 FallbackRecord::cpu(
157 FallbackReason::CudaFeatureNotBuilt,
158 "GPU was required but the cuda feature is not enabled",
159 )
160 });
161 Err(GpuError::unavailable(fb.reason, fb.detail))
162 }
163 }
164 }
165
166 pub fn is_ready(&self) -> bool {
167 false
168 }
169
170 pub fn capabilities(&self) -> &CapabilityReport {
171 &self.capabilities
172 }
173
174 pub fn selected_backend(&self) -> Backend {
175 self.capabilities.selected_backend
176 }
177
178 pub fn fallback(&self) -> Option<&FallbackRecord> {
179 self.capabilities.fallback.as_ref()
180 }
181
182 fn unavailable_error(&self) -> GpuError {
183 match &self.capabilities.fallback {
184 Some(fb) => GpuError::unavailable(fb.reason, fb.detail.clone()),
185 None => GpuError::NoGpu,
186 }
187 }
188
189 pub fn kernels(&self) -> GpuResult<&KernelModule> {
190 Err(self.unavailable_error())
191 }
192 #[allow(clippy::too_many_arguments)]
193 pub fn stdp_update(
194 &self,
195 _: &mut GpuBuffer<f32>,
196 _: &GpuBuffer<f32>,
197 _: &GpuBuffer<f32>,
198 _: &mut GpuBuffer<f32>,
199 _: &mut GpuBuffer<f32>,
200 _: i32,
201 _: i32,
202 _: f32,
203 ) -> GpuResult<()> {
204 Err(self.unavailable_error())
205 }
206 #[allow(clippy::too_many_arguments)]
207 pub fn stdp_update_async(
208 &self,
209 _: &mut GpuBuffer<f32>,
210 _: &GpuBuffer<f32>,
211 _: &GpuBuffer<f32>,
212 _: &mut GpuBuffer<f32>,
213 _: &mut GpuBuffer<f32>,
214 _: i32,
215 _: i32,
216 _: f32,
217 ) -> GpuResult<()> {
218 Err(self.unavailable_error())
219 }
220 pub fn satsolver_extract(
221 &self,
222 _: &GpuBuffer<u8>,
223 _: &GpuBuffer<i32>,
224 _: &mut GpuBuffer<u8>,
225 _: i32,
226 _: i32,
227 ) -> GpuResult<()> {
228 Err(self.unavailable_error())
229 }
230 #[allow(clippy::too_many_arguments)]
231 pub fn satsolver_aux_reduce_best(
232 &self,
233 _: &GpuBuffer<u8>,
234 _: &mut GpuBuffer<u8>,
235 _: &GpuBuffer<i32>,
236 _: &mut GpuBuffer<i32>,
237 _: &mut GpuBuffer<i32>,
238 _: &GpuBuffer<i32>,
239 _: i32,
240 _: i32,
241 _: i32,
242 _: i32,
243 ) -> GpuResult<()> {
244 Err(self.unavailable_error())
245 }
246 pub fn poisson_encode(
247 &self,
248 _: &GpuBuffer<f32>,
249 _: &mut GpuBuffer<u32>,
250 _: u32,
251 ) -> GpuResult<()> {
252 Err(self.unavailable_error())
253 }
254
255 pub fn satsolver_extract_async(
256 &self,
257 _: &GpuBuffer<u8>,
258 _: &GpuBuffer<i32>,
259 _: &mut GpuBuffer<u8>,
260 _: i32,
261 _: i32,
262 ) -> GpuResult<()> {
263 Err(self.unavailable_error())
264 }
265
266 #[allow(clippy::too_many_arguments)]
267 pub fn satsolver_aux_reduce_best_async(
268 &self,
269 _: &GpuBuffer<u8>,
270 _: &mut GpuBuffer<u8>,
271 _: &GpuBuffer<i32>,
272 _: &mut GpuBuffer<i32>,
273 _: &mut GpuBuffer<i32>,
274 _: &GpuBuffer<i32>,
275 _: i32,
276 _: i32,
277 _: i32,
278 _: i32,
279 ) -> GpuResult<()> {
280 Err(self.unavailable_error())
281 }
282
283 pub fn poisson_encode_async(
284 &self,
285 _: &GpuBuffer<f32>,
286 _: &mut GpuBuffer<u32>,
287 _: u32,
288 ) -> GpuResult<()> {
289 Err(self.unavailable_error())
290 }
291
292 #[allow(clippy::too_many_arguments)]
293 pub fn ternary_gemv(
294 &self,
295 _: &GpuBuffer<u32>,
296 _: &GpuBuffer<f32>,
297 _: &GpuBuffer<f32>,
298 _: &mut GpuBuffer<f32>,
299 _: i32,
300 _: i32,
301 _: i32,
302 _: bool,
303 ) -> GpuResult<()> {
304 Err(self.unavailable_error())
305 }
306
307 #[allow(clippy::too_many_arguments)]
308 pub fn ternary_gemv_async(
309 &self,
310 _: &GpuBuffer<u32>,
311 _: &GpuBuffer<f32>,
312 _: &GpuBuffer<f32>,
313 _: &mut GpuBuffer<f32>,
314 _: i32,
315 _: i32,
316 _: i32,
317 _: bool,
318 ) -> GpuResult<()> {
319 Err(self.unavailable_error())
320 }
321
322 #[allow(clippy::too_many_arguments)]
323 pub fn ternary_gemm(
324 &self,
325 _: &GpuBuffer<u32>,
326 _: &GpuBuffer<f32>,
327 _: &GpuBuffer<f32>,
328 _: &mut GpuBuffer<f32>,
329 _: i32,
330 _: i32,
331 _: i32,
332 _: i32,
333 _: bool,
334 ) -> GpuResult<()> {
335 Err(self.unavailable_error())
336 }
337
338 #[allow(clippy::too_many_arguments)]
339 pub fn ternary_gemm_async(
340 &self,
341 _: &GpuBuffer<u32>,
342 _: &GpuBuffer<f32>,
343 _: &GpuBuffer<f32>,
344 _: &mut GpuBuffer<f32>,
345 _: i32,
346 _: i32,
347 _: i32,
348 _: i32,
349 _: bool,
350 ) -> GpuResult<()> {
351 Err(self.unavailable_error())
352 }
353
354 pub fn synchronize(&self) -> GpuResult<()> {
355 Err(self.unavailable_error())
356 }
357}
358impl Default for GpuAccelerator {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn context_api_reports_no_compute_capability() {
370 let ctx = GpuContext;
371 assert_eq!(ctx.compute_capability(), None);
372 }
373
374 #[test]
377 fn error_display_nogpu() {
378 let err = GpuError::NoGpu;
379 assert_eq!(
380 err.to_string(),
381 "No GPU available (built without `cuda` feature)"
382 );
383 }
384
385 #[test]
386 fn error_display_init_failed() {
387 let err = GpuError::InitFailed("device busy".into());
388 assert_eq!(err.to_string(), "GPU init failed: device busy");
389 }
390
391 #[test]
392 fn error_display_module_load_failed() {
393 let err = GpuError::ModuleLoadFailed("bad ptx".into());
394 assert_eq!(err.to_string(), "PTX module load failed: bad ptx");
395 }
396
397 #[test]
398 fn error_display_kernel_not_found() {
399 let err = GpuError::KernelNotFound("foo_kernel".into());
400 assert_eq!(err.to_string(), "Kernel not found: foo_kernel");
401 }
402
403 #[test]
404 fn error_display_memory_error() {
405 let err = GpuError::MemoryError("OOM".into());
406 assert_eq!(err.to_string(), "GPU memory error: OOM");
407 }
408
409 #[test]
410 fn error_display_launch_failed() {
411 let err = GpuError::LaunchFailed("grid too large".into());
412 assert_eq!(err.to_string(), "Kernel launch failed: grid too large");
413 }
414
415 #[test]
416 fn error_display_cuda_error() {
417 let err = GpuError::CudaError("illegal memory".into());
418 assert_eq!(err.to_string(), "CUDA error: illegal memory");
419 }
420
421 #[test]
422 fn error_display_unavailable() {
423 let err = GpuError::unavailable(
424 FallbackReason::CudaFeatureNotBuilt,
425 "crate built without the cuda feature",
426 );
427 assert_eq!(
428 err.to_string(),
429 "GPU unavailable (cuda_feature_not_built): crate built without the cuda feature"
430 );
431 }
432
433 #[test]
434 fn error_display_invalid_input() {
435 let err = GpuError::invalid_input("n_vars must be >= 0, got -1");
436 assert_eq!(
437 err.to_string(),
438 "invalid input: n_vars must be >= 0, got -1"
439 );
440 }
441
442 #[test]
443 fn error_is_std_error() {
444 let err: &dyn std::error::Error = &GpuError::NoGpu;
445 assert!(!err.to_string().is_empty());
446 }
447
448 #[test]
451 fn context_init_returns_no_gpu() {
452 let result = GpuContext::init();
453 assert!(result.is_err());
454 assert!(matches!(result.unwrap_err(), GpuError::NoGpu));
455 }
456
457 #[test]
458 fn context_is_available_returns_false() {
459 assert!(!GpuContext::is_available());
460 }
461
462 #[test]
465 fn kernel_module_load_returns_no_gpu() {
466 let result = KernelModule::load();
467 assert!(matches!(result.unwrap_err(), GpuError::NoGpu));
468 }
469
470 #[test]
471 fn kernel_module_load_satsolver_returns_no_gpu() {
472 let result = KernelModule::load_satsolver();
473 assert!(matches!(result.unwrap_err(), GpuError::NoGpu));
474 }
475
476 #[test]
479 fn buffer_alloc_zero_length() {
480 let buf = GpuBuffer::<u8>::alloc(0).unwrap();
481 assert_eq!(buf.len(), 0);
482 }
483
484 #[test]
485 fn buffer_alloc_u8() {
486 let buf = GpuBuffer::<u8>::alloc(100).unwrap();
487 assert_eq!(buf.len(), 100);
488 let data = buf.to_vec().unwrap();
489 assert_eq!(data, vec![0u8; 100]);
490 }
491
492 #[test]
493 fn buffer_alloc_f32() {
494 let buf = GpuBuffer::<f32>::alloc(64).unwrap();
495 assert_eq!(buf.len(), 64);
496 let data = buf.to_vec().unwrap();
497 assert!(data.iter().all(|&v| v == 0.0f32));
498 }
499
500 #[test]
504 fn buffer_alloc_overflow_returns_memory_error() {
505 for len in [usize::MAX, isize::MAX as usize] {
507 assert_alloc_overflow::<u64>(len);
508 }
509 for len in [usize::MAX, isize::MAX as usize + 1] {
511 assert_alloc_overflow::<u8>(len);
512 }
513 assert_alloc_overflow::<Wide512>(usize::MAX);
515 }
516
517 fn assert_alloc_overflow<T: Default + Clone>(len: usize) {
519 match GpuBuffer::<T>::alloc(len) {
520 Err(GpuError::MemoryError(msg)) => {
521 assert!(msg.contains("size overflow"), "msg: {msg}")
522 }
523 Err(other) => panic!("expected MemoryError, got {other}"),
524 Ok(_) => panic!("oversized alloc unexpectedly succeeded"),
525 }
526 }
527
528 #[derive(Clone)]
530 struct Wide512 {
531 _payload: [u64; 512],
532 }
533
534 impl Default for Wide512 {
535 fn default() -> Self {
536 Self {
537 _payload: [0u64; 512],
538 }
539 }
540 }
541
542 #[test]
544 fn buffer_alloc_zero_sized_type() {
545 let empty = GpuBuffer::<()>::alloc(0).unwrap();
546 assert_eq!(empty.len(), 0);
547 assert!(empty.to_vec().unwrap().is_empty());
548 let buf = GpuBuffer::<()>::alloc(8).unwrap();
549 assert_eq!(buf.len(), 8);
550 assert_eq!(buf.to_vec().unwrap(), vec![(); 8]);
551 }
552
553 #[test]
554 fn buffer_from_slice_roundtrip() {
555 let input = vec![1i32, 2, 3, 4, 5];
556 let buf = GpuBuffer::from_slice(&input).unwrap();
557 assert_eq!(buf.len(), 5);
558 let output = buf.to_vec().unwrap();
559 assert_eq!(input, output);
560 }
561
562 #[test]
563 fn buffer_from_slice_empty() {
564 let buf = GpuBuffer::<u8>::from_slice(&[]).unwrap();
565 assert_eq!(buf.len(), 0);
566 }
567
568 #[test]
569 fn buffer_upload_ok() {
570 let mut buf = GpuBuffer::<i32>::alloc(4).unwrap();
571 buf.upload(&[10, 20, 30, 40]).unwrap();
572 assert_eq!(buf.to_vec().unwrap(), vec![10, 20, 30, 40]);
573 }
574
575 #[test]
576 fn buffer_upload_length_mismatch() {
577 let mut buf = GpuBuffer::<i32>::alloc(4).unwrap();
578 let result = buf.upload(&[1, 2]);
579 assert!(result.is_err());
580 match result.unwrap_err() {
581 GpuError::MemoryError(msg) => {
582 assert!(msg.contains("length mismatch"));
583 assert!(msg.contains("4"));
584 assert!(msg.contains("2"));
585 }
586 other => panic!("expected MemoryError, got: {other}"),
587 }
588 }
589
590 fn assert_length_mismatch(err: GpuError, buffer_len: usize, input_len: usize) {
594 match err {
595 GpuError::MemoryError(msg) => {
596 assert!(msg.contains("length mismatch"), "msg: {msg}");
597 assert!(
598 msg.contains(&format!("buffer has {buffer_len} elements")),
599 "msg: {msg}"
600 );
601 assert!(
602 msg.contains(&format!("input has {input_len}")),
603 "msg: {msg}"
604 );
605 }
606 other => panic!("expected MemoryError, got {other}"),
607 }
608 }
609
610 #[test]
613 fn buffer_alloc_boundary_lengths_read_back_default() {
614 for len in [0usize, 1, 2, 15, 16, 17, 256, 257, 4096] {
615 let buf = GpuBuffer::<i32>::alloc(len).unwrap();
616 assert_eq!(buf.len(), len, "len {len}");
617 assert_eq!(buf.is_empty(), len == 0, "is_empty at len {len}");
618 assert_eq!(
619 buf.to_vec().unwrap(),
620 vec![0i32; len],
621 "readback at len {len}"
622 );
623 }
624 }
625
626 #[test]
628 fn buffer_from_slice_to_vec_roundtrip_boundaries() {
629 for len in [0usize, 1, 16, 17, 128, 257] {
630 let input: Vec<u32> = (0..len as u32)
631 .map(|i| i.wrapping_mul(2_654_435_761))
632 .collect();
633 let buf = GpuBuffer::from_slice(&input).unwrap();
634 assert_eq!(buf.to_vec().unwrap(), input, "roundtrip at len {len}");
635 }
636 }
637
638 #[test]
640 fn buffer_upload_equal_length_replaces_all() {
641 let mut buf = GpuBuffer::from_slice(&[9i32, 9, 9, 9]).unwrap();
642 buf.upload(&[1, 2, 3, 4]).unwrap();
643 assert_eq!(buf.to_vec().unwrap(), vec![1, 2, 3, 4]);
644 }
645
646 #[test]
649 fn buffer_upload_too_short_rejected_and_non_mutating() {
650 let mut buf = GpuBuffer::from_slice(&[1i32, 2, 3, 4]).unwrap();
651 assert_length_mismatch(buf.upload(&[7, 8]).unwrap_err(), 4, 2);
652 assert_eq!(buf.to_vec().unwrap(), vec![1, 2, 3, 4], "contents mutated");
653 }
654
655 #[test]
657 fn buffer_upload_too_long_rejected_and_non_mutating() {
658 let mut buf = GpuBuffer::from_slice(&[1i32, 2, 3, 4]).unwrap();
659 assert_length_mismatch(buf.upload(&[7, 8, 9, 10, 11]).unwrap_err(), 4, 5);
660 assert_eq!(buf.to_vec().unwrap(), vec![1, 2, 3, 4], "contents mutated");
661 }
662
663 #[test]
665 fn buffer_upload_empty_into_empty_ok() {
666 let mut buf = GpuBuffer::<i32>::alloc(0).unwrap();
667 buf.upload(&[]).unwrap();
668 assert!(buf.to_vec().unwrap().is_empty());
669 }
670
671 #[test]
673 fn buffer_upload_empty_nonempty_mismatches_fail() {
674 let mut empty = GpuBuffer::<i32>::alloc(0).unwrap();
675 assert_length_mismatch(empty.upload(&[1]).unwrap_err(), 0, 1);
676
677 let mut nonempty = GpuBuffer::<i32>::alloc(3).unwrap();
678 assert_length_mismatch(nonempty.upload(&[]).unwrap_err(), 3, 0);
679 }
680
681 #[test]
682 fn buffer_zero_prefix_preserves_tail() {
683 let mut buf = GpuBuffer::<f32>::from_slice(&[1.0, 2.0, 3.0, 4.0]).unwrap();
684 buf.zero_prefix(2).unwrap();
685 assert_eq!(buf.to_vec().unwrap(), vec![0.0, 0.0, 3.0, 4.0]);
686 }
687
688 #[test]
689 fn buffer_zero_prefix_rejects_oversize() {
690 let mut buf = GpuBuffer::<f32>::from_slice(&[1.0, 2.0]).unwrap();
691 let err = buf.zero_prefix(3).unwrap_err();
692 match err {
693 GpuError::MemoryError(msg) => assert!(msg.contains("zero_prefix")),
694 other => panic!("expected MemoryError, got {other}"),
695 }
696 assert_eq!(buf.to_vec().unwrap(), vec![1.0, 2.0]);
697 }
698
699 #[test]
700 fn buffer_as_device_ptr_non_null() {
701 let buf = GpuBuffer::<u8>::alloc(16).unwrap();
702 let ptr = buf.as_device_ptr();
703 assert!(!ptr.is_null());
704 }
705
706 #[test]
709 fn accelerator_new() {
710 let acc = GpuAccelerator::new();
711 assert!(!acc.is_ready());
712 assert_eq!(acc.selected_backend(), Backend::Cpu);
713 let fb = acc.fallback().expect("CPU stub records a fallback");
714 assert_eq!(fb.reason, FallbackReason::CudaFeatureNotBuilt);
715 assert_eq!(fb.selected_backend, Backend::Cpu);
716 assert!(!fb.detail.contains("/home/"));
717 assert!(!acc.capabilities().cuda_built);
718 }
719
720 #[test]
721 fn accelerator_default() {
722 let acc = GpuAccelerator::default();
723 assert!(!acc.is_ready());
724 }
725
726 #[test]
727 fn accelerator_require_gpu_fails_closed() {
728 let err = match GpuAccelerator::require_gpu() {
729 Err(e) => e,
730 Ok(_) => panic!("require_gpu must fail on the CPU stub"),
731 };
732 assert_eq!(
733 err.fallback_reason(),
734 Some(FallbackReason::CudaFeatureNotBuilt)
735 );
736 match err {
737 GpuError::Unavailable { reason, detail } => {
738 assert_eq!(reason, FallbackReason::CudaFeatureNotBuilt);
739 assert!(!detail.contains("/home/"));
740 }
741 other => panic!("expected Unavailable, got {other}"),
742 }
743 }
744
745 #[test]
746 fn accelerator_with_policy_prefer_gpu_is_cpu() {
747 let acc = GpuAccelerator::with_policy(ExecutionPolicy::PreferGpu).unwrap();
748 assert_eq!(acc.selected_backend(), Backend::Cpu);
749 assert!(!acc.is_ready());
750 }
751
752 #[test]
753 fn accelerator_kernels_returns_unavailable() {
754 let acc = GpuAccelerator::new();
755 assert_unavailable_not_built(acc.kernels().unwrap_err());
756 }
757
758 #[test]
759 fn accelerator_synchronize_returns_unavailable() {
760 let acc = GpuAccelerator::new();
761 assert_unavailable_not_built(acc.synchronize().unwrap_err());
762 }
763
764 fn assert_unavailable_not_built(err: GpuError) {
765 match err {
766 GpuError::Unavailable { reason, detail } => {
767 assert_eq!(reason, FallbackReason::CudaFeatureNotBuilt);
768 assert!(!detail.contains("/home/"));
769 assert!(!detail.contains("/Users/"));
770 }
771 other => panic!("expected Unavailable(cuda_feature_not_built), got {other}"),
772 }
773 }
774
775 #[test]
776 fn accelerator_satsolver_extract_returns_unavailable() {
777 let acc = GpuAccelerator::new();
778 let assignment = GpuBuffer::<u8>::alloc(10).unwrap();
779 let best_walker = GpuBuffer::<i32>::alloc(1).unwrap();
780 let mut output = GpuBuffer::<u8>::alloc(10).unwrap();
781 assert_unavailable_not_built(
782 acc.satsolver_extract(&assignment, &best_walker, &mut output, 10, 1)
783 .unwrap_err(),
784 );
785 }
786
787 #[test]
788 fn accelerator_poisson_encode_returns_unavailable() {
789 let acc = GpuAccelerator::new();
790 let stimuli = GpuBuffer::<f32>::alloc(10).unwrap();
791 let mut spikes = GpuBuffer::<u32>::alloc(10).unwrap();
792 assert_unavailable_not_built(acc.poisson_encode(&stimuli, &mut spikes, 42).unwrap_err());
793 }
794
795 #[test]
796 fn accelerator_satsolver_extract_async_returns_unavailable() {
797 let acc = GpuAccelerator::new();
798 let assignment = GpuBuffer::<u8>::alloc(10).unwrap();
799 let best_walker = GpuBuffer::<i32>::alloc(1).unwrap();
800 let mut output = GpuBuffer::<u8>::alloc(10).unwrap();
801 assert_unavailable_not_built(
802 acc.satsolver_extract_async(&assignment, &best_walker, &mut output, 10, 1)
803 .unwrap_err(),
804 );
805 }
806
807 #[test]
808 fn accelerator_poisson_encode_async_returns_unavailable() {
809 let acc = GpuAccelerator::new();
810 let stimuli = GpuBuffer::<f32>::alloc(10).unwrap();
811 let mut spikes = GpuBuffer::<u32>::alloc(10).unwrap();
812 assert_unavailable_not_built(
813 acc.poisson_encode_async(&stimuli, &mut spikes, 42)
814 .unwrap_err(),
815 );
816 }
817
818 #[test]
819 fn accelerator_ternary_gemv_returns_unavailable() {
820 let acc = GpuAccelerator::new();
821 let w = GpuBuffer::<u32>::alloc(1).unwrap();
822 let s = GpuBuffer::<f32>::alloc(1).unwrap();
823 let x = GpuBuffer::<f32>::alloc(1).unwrap();
824 let mut y = GpuBuffer::<f32>::alloc(1).unwrap();
825 assert_unavailable_not_built(
826 acc.ternary_gemv(&w, &s, &x, &mut y, 1, 1, 1, false)
827 .unwrap_err(),
828 );
829 }
830
831 #[test]
832 fn accelerator_ternary_gemm_returns_unavailable() {
833 let acc = GpuAccelerator::new();
834 let w = GpuBuffer::<u32>::alloc(1).unwrap();
835 let s = GpuBuffer::<f32>::alloc(1).unwrap();
836 let b = GpuBuffer::<f32>::alloc(1).unwrap();
837 let mut c = GpuBuffer::<f32>::alloc(1).unwrap();
838 assert_unavailable_not_built(
839 acc.ternary_gemm(&w, &s, &b, &mut c, 1, 1, 1, 1, false)
840 .unwrap_err(),
841 );
842 }
843
844 #[test]
845 fn accelerator_ternary_gemv_async_returns_unavailable() {
846 let acc = GpuAccelerator::new();
847 let w = GpuBuffer::<u32>::alloc(1).unwrap();
848 let s = GpuBuffer::<f32>::alloc(1).unwrap();
849 let x = GpuBuffer::<f32>::alloc(1).unwrap();
850 let mut y = GpuBuffer::<f32>::alloc(1).unwrap();
851 assert_unavailable_not_built(
852 acc.ternary_gemv_async(&w, &s, &x, &mut y, 1, 1, 1, false)
853 .unwrap_err(),
854 );
855 }
856
857 #[test]
858 fn accelerator_ternary_gemm_async_returns_unavailable() {
859 let acc = GpuAccelerator::new();
860 let w = GpuBuffer::<u32>::alloc(1).unwrap();
861 let s = GpuBuffer::<f32>::alloc(1).unwrap();
862 let b = GpuBuffer::<f32>::alloc(1).unwrap();
863 let mut c = GpuBuffer::<f32>::alloc(1).unwrap();
864 assert_unavailable_not_built(
865 acc.ternary_gemm_async(&w, &s, &b, &mut c, 1, 1, 1, 1, false)
866 .unwrap_err(),
867 );
868 }
869
870 use proptest::prelude::*;
873
874 proptest! {
875 #[test]
876 fn buffer_from_slice_roundtrip_prop(data in proptest::collection::vec(any::<i32>(), 0..=256)) {
877 let buf = GpuBuffer::from_slice(&data).unwrap();
878 prop_assert_eq!(buf.len(), data.len());
879 prop_assert_eq!(buf.to_vec().unwrap(), data);
880 }
881
882 #[test]
883 fn buffer_alloc_has_correct_len(n in 0usize..=1024) {
884 let buf = GpuBuffer::<u8>::alloc(n).unwrap();
885 prop_assert_eq!(buf.len(), n);
886 }
887
888 #[test]
889 fn buffer_upload_roundtrip(
890 data in proptest::collection::vec(
892 any::<f32>().prop_filter("finite", |v| v.is_finite()),
893 1..=128
894 )
895 ) {
896 let mut buf = GpuBuffer::<f32>::alloc(data.len()).unwrap();
897 buf.upload(&data).unwrap();
898 prop_assert_eq!(buf.to_vec().unwrap(), data);
899 }
900 }
901}