1#[cfg(all(
26 feature = "native-cuda",
27 any(target_os = "linux", target_os = "windows")
28))]
29pub mod cuda_attn_kernels;
30#[cfg(all(
31 feature = "native-cuda",
32 any(target_os = "linux", target_os = "windows")
33))]
34pub mod cuda_fp8_kernels;
35#[cfg(all(
36 feature = "native-cuda",
37 any(target_os = "linux", target_os = "windows")
38))]
39pub mod cuda_fp8_prefill;
40#[cfg(all(
41 feature = "native-cuda",
42 any(target_os = "linux", target_os = "windows")
43))]
44pub mod cuda_fp8_prefill_kernels;
45#[cfg(all(
46 feature = "native-cuda",
47 any(target_os = "linux", target_os = "windows")
48))]
49pub mod cuda_full_layer;
50#[cfg(all(
51 feature = "native-cuda",
52 any(target_os = "linux", target_os = "windows")
53))]
54pub mod cuda_graph;
55#[cfg(all(
56 feature = "native-cuda",
57 any(target_os = "linux", target_os = "windows")
58))]
59pub mod cuda_imagen_attn_kernels;
60#[cfg(all(
61 feature = "native-cuda",
62 any(target_os = "linux", target_os = "windows")
63))]
64pub mod cuda_imagen_dit_glue_kernels;
65#[cfg(all(
66 feature = "native-cuda",
67 any(target_os = "linux", target_os = "windows")
68))]
69pub mod cuda_imagen_gemm_kernels;
70#[cfg(all(
71 feature = "native-cuda",
72 any(target_os = "linux", target_os = "windows")
73))]
74pub mod cuda_imagen_vae_kernels;
75#[cfg(all(
76 feature = "native-cuda",
77 any(target_os = "linux", target_os = "windows")
78))]
79pub mod cuda_k_quant_kernels;
80#[cfg(all(
81 feature = "native-cuda",
82 any(target_os = "linux", target_os = "windows")
83))]
84pub mod cuda_k_quant_prefill;
85#[cfg(all(
86 feature = "native-cuda",
87 any(target_os = "linux", target_os = "windows")
88))]
89pub mod cuda_k_quant_prefill_kernels;
90#[cfg(all(
91 feature = "native-cuda",
92 any(target_os = "linux", target_os = "windows")
93))]
94pub mod cuda_kernels;
95#[cfg(all(
96 feature = "native-cuda",
97 any(target_os = "linux", target_os = "windows")
98))]
99pub mod cuda_prefill;
100#[cfg(all(
101 feature = "native-cuda",
102 any(target_os = "linux", target_os = "windows")
103))]
104pub mod cuda_prefill_kernels;
105#[cfg(all(
106 feature = "native-cuda",
107 any(target_os = "linux", target_os = "windows")
108))]
109pub mod cuda_q_std_kernels;
110#[cfg(all(
111 feature = "native-cuda",
112 any(target_os = "linux", target_os = "windows")
113))]
114pub mod cuda_q_std_prefill;
115#[cfg(all(
116 feature = "native-cuda",
117 any(target_os = "linux", target_os = "windows")
118))]
119pub mod cuda_q_std_prefill_kernels;
120pub mod kernel_sources;
121#[cfg(all(feature = "metal", target_os = "macos"))]
122mod metal_dispatch;
123#[cfg(all(feature = "metal", target_os = "macos"))]
124pub mod metal_fp8_kernels;
125#[cfg(all(feature = "metal", target_os = "macos"))]
126pub mod metal_fp8_prefill;
127#[cfg(all(feature = "metal", target_os = "macos"))]
128pub mod metal_full_layer;
129#[cfg(all(feature = "metal", target_os = "macos"))]
130pub mod metal_graph;
131#[cfg(all(feature = "metal", target_os = "macos"))]
132mod metal_prefill;
133pub mod scirs2_backend;
134
135use thiserror::Error;
136#[allow(unused_imports)]
137use tracing::warn;
138
139#[cfg(feature = "gpu")]
140pub use scirs2_backend::Scirs2Backend;
141
142#[cfg(all(feature = "metal", target_os = "macos"))]
143pub use metal_fp8_kernels::{metal_gemv_fp8_e4m3, metal_gemv_fp8_e5m2};
144
145#[cfg(all(feature = "metal", target_os = "macos"))]
146pub use metal_fp8_prefill::{
147 metal_fused_gate_up_swiglu_fp8_e4m3, metal_fused_gate_up_swiglu_fp8_e5m2, metal_gemm_fp8_e4m3,
148 metal_gemm_fp8_e4m3_residual, metal_gemm_fp8_e5m2, metal_gemm_fp8_e5m2_residual,
149};
150
151#[cfg(all(feature = "metal", target_os = "macos"))]
152pub use metal_graph::{MetalGraph, MetalGraphError, MetalWeightHandle};
153
154#[cfg(all(feature = "metal", target_os = "macos"))]
155pub use metal_full_layer::{
156 build_cached_weights, build_cached_weights_ternary_only, print_gpu_profile_summary,
157 try_metal_ffn, try_metal_forward_greedy_ternary, try_metal_full_forward,
158 try_metal_full_forward_cached, try_metal_full_forward_ternary, try_metal_full_layer,
159 try_metal_prefill_ternary, try_metal_prefill_verify_ternary, try_metal_qkv, CachedLayerWeights,
160 CachedModelWeights, FullForwardLayerParams, FullForwardLayerParamsTernary,
161};
162
163#[cfg(all(feature = "metal", target_os = "macos"))]
164pub use metal_prefill::{
165 try_metal_full_forward_prefill, try_metal_full_forward_prefill_ternary,
166 try_metal_full_forward_prefill_verify, try_metal_full_forward_prefill_verify_ternary,
167};
168
169#[cfg(all(
170 feature = "native-cuda",
171 any(target_os = "linux", target_os = "windows")
172))]
173pub use cuda_graph::{
174 try_cuda_ffn, try_cuda_qkv, CudaGraph, CudaGraphError, DitSingleBlockWeights, NativeCudaBackend,
175};
176
177#[cfg(all(
178 feature = "native-cuda",
179 any(target_os = "linux", target_os = "windows")
180))]
181pub use cuda_full_layer::{
182 try_cuda_full_forward, try_cuda_full_forward_ternary,
183 try_cuda_full_forward_ternary_with_gpu_lm_head, try_cuda_full_forward_with_gpu_lm_head,
184 try_cuda_full_layer, CudaCachedLayerWeights, CudaFullForwardLayerParams,
185 CudaFullForwardLayerParamsTernary,
186};
187
188#[cfg(all(
189 feature = "native-cuda",
190 any(target_os = "linux", target_os = "windows")
191))]
192pub use cuda_prefill::{try_cuda_prefill, try_cuda_prefill_ternary};
193
194#[cfg(all(
195 feature = "native-cuda",
196 any(target_os = "linux", target_os = "windows")
197))]
198pub use cuda_fp8_kernels::{cuda_gemv_fp8_e4m3, cuda_gemv_fp8_e5m2};
199
200#[cfg(all(
201 feature = "native-cuda",
202 any(target_os = "linux", target_os = "windows")
203))]
204pub use cuda_k_quant_kernels::{
205 cuda_gemv_q2k, cuda_gemv_q3k, cuda_gemv_q4k, cuda_gemv_q5k, cuda_gemv_q6k, cuda_gemv_q8k,
206};
207#[cfg(all(
208 feature = "native-cuda",
209 any(target_os = "linux", target_os = "windows")
210))]
211pub use cuda_q_std_kernels::{cuda_gemv_q4_0, cuda_gemv_q8_0};
212
213#[cfg(all(
214 feature = "native-cuda",
215 any(target_os = "linux", target_os = "windows")
216))]
217pub use cuda_q_std_prefill::{try_cuda_prefill_q_std, CudaQStdPrefillLayerParams};
218
219#[cfg(all(
220 feature = "native-cuda",
221 any(target_os = "linux", target_os = "windows")
222))]
223pub use cuda_k_quant_prefill::{
224 try_cuda_prefill_k_quant, CudaKQuantPrefillLayerParams, KQuantFormat,
225};
226
227#[cfg(all(
228 feature = "native-cuda",
229 any(target_os = "linux", target_os = "windows")
230))]
231pub use cuda_fp8_prefill::{try_cuda_prefill_fp8, CudaFP8PrefillLayerParams};
232
233pub struct DeviceBuffer {
243 pub data: Vec<f32>,
245 pub size: usize,
247 pub device_id: usize,
249}
250
251impl DeviceBuffer {
252 pub fn new(size: usize, device_id: usize) -> Self {
254 Self {
255 data: vec![0.0_f32; size],
256 size,
257 device_id,
258 }
259 }
260
261 pub fn from_slice(data: &[f32], device_id: usize) -> Self {
263 let size = data.len();
264 Self {
265 data: data.to_vec(),
266 size,
267 device_id,
268 }
269 }
270
271 pub fn to_vec(&self) -> Vec<f32> {
273 self.data.clone()
274 }
275
276 pub fn size(&self) -> usize {
278 self.size
279 }
280
281 pub fn device_id(&self) -> usize {
283 self.device_id
284 }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub struct LaunchConfig {
297 pub grid_dim: (u32, u32, u32),
299 pub block_dim: (u32, u32, u32),
301 pub shared_mem_bytes: u32,
303}
304
305const DEFAULT_BLOCK_SIZE: u32 = 256;
307
308impl LaunchConfig {
309 pub fn for_n_elements(n: usize) -> Self {
314 let block = DEFAULT_BLOCK_SIZE;
315 let grid = ((n as u32).saturating_add(block - 1)) / block;
316 Self {
317 grid_dim: (grid.max(1), 1, 1),
318 block_dim: (block, 1, 1),
319 shared_mem_bytes: 0,
320 }
321 }
322
323 pub fn default_1d() -> Self {
325 Self {
326 grid_dim: (1, 1, 1),
327 block_dim: (DEFAULT_BLOCK_SIZE, 1, 1),
328 shared_mem_bytes: 0,
329 }
330 }
331}
332
333#[derive(Debug, Error)]
339pub enum GpuError {
340 #[error("GPU not available: {0}")]
342 NotAvailable(String),
343
344 #[error("out of device memory: requested {requested} bytes on device {device}")]
346 OutOfMemory {
347 requested: usize,
349 device: usize,
351 },
352
353 #[error("kernel launch failed: {0}")]
355 KernelLaunch(String),
356
357 #[error("device synchronization failed: {0}")]
359 SyncFailed(String),
360
361 #[error("invalid argument: {0}")]
363 InvalidArgument(String),
364}
365
366pub trait GpuBackendTrait: Send + Sync {
381 fn name(&self) -> &'static str;
383
384 fn is_accelerated(&self) -> bool;
386
387 fn device_count(&self) -> usize;
389
390 fn alloc(&self, size: usize, device_id: usize) -> Result<DeviceBuffer, GpuError>;
392
393 fn host_to_device(&self, src: &[f32], device_id: usize) -> Result<DeviceBuffer, GpuError>;
395
396 fn device_to_host(&self, buf: &DeviceBuffer) -> Result<Vec<f32>, GpuError>;
398
399 fn matvec(
405 &self,
406 a: &DeviceBuffer,
407 x: &DeviceBuffer,
408 m: usize,
409 k: usize,
410 device_id: usize,
411 ) -> Result<DeviceBuffer, GpuError>;
412
413 fn relu(&self, x: &DeviceBuffer, device_id: usize) -> Result<DeviceBuffer, GpuError>;
415
416 fn softmax(
418 &self,
419 x: &DeviceBuffer,
420 size: usize,
421 device_id: usize,
422 ) -> Result<DeviceBuffer, GpuError>;
423
424 fn synchronize(&self, device_id: usize) -> Result<(), GpuError>;
426
427 fn memory_info(&self, device_id: usize) -> Result<(usize, usize), GpuError>;
429
430 fn gemv_q1_g128(
435 &self,
436 block_bytes: &[u8],
437 input: &[f32],
438 n_rows: usize,
439 k: usize,
440 ) -> Result<Vec<f32>, GpuError> {
441 cpu_gemv_1bit_fallback(block_bytes, input, n_rows, k)
442 }
443
444 fn gemm_q1_g128(
448 &self,
449 block_bytes: &[u8],
450 input: &[f32],
451 m: usize,
452 n_rows: usize,
453 k: usize,
454 ) -> Result<Vec<f32>, GpuError> {
455 let mut output = vec![0.0_f32; m * n_rows];
456 for i in 0..m {
457 let row_input = &input[i * k..(i + 1) * k];
458 let row_output = self.gemv_q1_g128(block_bytes, row_input, n_rows, k)?;
459 output[i * n_rows..(i + 1) * n_rows].copy_from_slice(&row_output);
460 }
461 Ok(output)
462 }
463
464 fn upload_weights_raw(
468 &self,
469 _block_bytes: &[u8],
470 ) -> Result<crate::weight_cache::GpuWeightHandle, GpuError> {
471 Err(GpuError::NotAvailable(
472 "weight caching not supported by this backend".into(),
473 ))
474 }
475
476 fn gemv_q1_g128_cached(
480 &self,
481 _handle: crate::weight_cache::GpuWeightHandle,
482 _input: &[f32],
483 _n_rows: usize,
484 _k: usize,
485 ) -> Result<Vec<f32>, GpuError> {
486 Err(GpuError::NotAvailable(
487 "cached GEMV not supported by this backend".into(),
488 ))
489 }
490
491 fn upload_weights_ternary(
495 &self,
496 _blocks: &[pictor_core::BlockTQ2_0_g128],
497 ) -> Result<crate::weight_cache::GpuWeightHandle, GpuError> {
498 Err(GpuError::NotAvailable(
499 "ternary weight upload not supported by this backend".into(),
500 ))
501 }
502
503 fn gemv_tq2_g128_cached(
507 &self,
508 _handle: crate::weight_cache::GpuWeightHandle,
509 _input: &[f32],
510 _n_rows: usize,
511 _k: usize,
512 ) -> Result<Vec<f32>, GpuError> {
513 Err(GpuError::NotAvailable(
514 "cached ternary GEMV not supported by this backend".into(),
515 ))
516 }
517
518 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
523 fn batch_attn_phase(
524 &self,
525 _hidden: &[f32],
526 _norm_weight: &[f32],
527 _norm_eps: f32,
528 _qkv_handle: crate::weight_cache::GpuWeightHandle,
529 _q_rows: usize,
530 _k_rows: usize,
531 _h: usize,
532 ) -> Result<Option<(Vec<f32>, Vec<f32>, Vec<f32>)>, GpuError> {
533 Ok(None)
534 }
535
536 #[allow(clippy::too_many_arguments)]
541 fn batch_ffn_phase(
542 &self,
543 _hidden: &mut [f32],
544 _attn_out: &[f32],
545 _norm_weight: &[f32],
546 _norm_eps: f32,
547 _attn_proj_handle: crate::weight_cache::GpuWeightHandle,
548 _gate_up_handle: crate::weight_cache::GpuWeightHandle,
549 _down_handle: crate::weight_cache::GpuWeightHandle,
550 _h: usize,
551 _intermediate: usize,
552 _attn_proj_k: usize,
553 ) -> Result<bool, GpuError> {
554 Ok(false)
555 }
556}
557
558pub type GpuBackend = dyn GpuBackendTrait;
563
564pub struct CpuBackend {
572 pub simulated_memory_bytes: usize,
574}
575
576impl CpuBackend {
577 pub fn new() -> Self {
579 Self {
580 simulated_memory_bytes: 4 * 1024 * 1024 * 1024,
581 }
582 }
583
584 pub fn with_memory(bytes: usize) -> Self {
586 Self {
587 simulated_memory_bytes: bytes,
588 }
589 }
590}
591
592impl Default for CpuBackend {
593 fn default() -> Self {
594 Self::new()
595 }
596}
597
598impl GpuBackendTrait for CpuBackend {
599 fn name(&self) -> &'static str {
600 "cpu"
601 }
602
603 fn is_accelerated(&self) -> bool {
604 false
605 }
606
607 fn device_count(&self) -> usize {
608 1
609 }
610
611 fn alloc(&self, size: usize, device_id: usize) -> Result<DeviceBuffer, GpuError> {
612 Ok(DeviceBuffer::new(size, device_id))
613 }
614
615 fn host_to_device(&self, src: &[f32], device_id: usize) -> Result<DeviceBuffer, GpuError> {
616 Ok(DeviceBuffer::from_slice(src, device_id))
617 }
618
619 fn device_to_host(&self, buf: &DeviceBuffer) -> Result<Vec<f32>, GpuError> {
620 Ok(buf.to_vec())
621 }
622
623 fn matvec(
624 &self,
625 a: &DeviceBuffer,
626 x: &DeviceBuffer,
627 m: usize,
628 k: usize,
629 device_id: usize,
630 ) -> Result<DeviceBuffer, GpuError> {
631 if a.size() != m * k {
632 return Err(GpuError::InvalidArgument(format!(
633 "matrix buffer size {} does not match m={} k={}",
634 a.size(),
635 m,
636 k
637 )));
638 }
639 if x.size() != k {
640 return Err(GpuError::InvalidArgument(format!(
641 "vector buffer size {} does not match k={}",
642 x.size(),
643 k
644 )));
645 }
646
647 let mut result = vec![0.0_f32; m];
648 for (row, slot) in result.iter_mut().enumerate().take(m) {
649 let mut acc = 0.0_f32;
650 for col in 0..k {
651 acc += a.data[row * k + col] * x.data[col];
652 }
653 *slot = acc;
654 }
655
656 Ok(DeviceBuffer::from_slice(&result, device_id))
657 }
658
659 fn relu(&self, x: &DeviceBuffer, device_id: usize) -> Result<DeviceBuffer, GpuError> {
660 let result: Vec<f32> = x.data.iter().map(|&v| v.max(0.0)).collect();
661 Ok(DeviceBuffer::from_slice(&result, device_id))
662 }
663
664 fn softmax(
665 &self,
666 x: &DeviceBuffer,
667 size: usize,
668 device_id: usize,
669 ) -> Result<DeviceBuffer, GpuError> {
670 if x.size() != size {
671 return Err(GpuError::InvalidArgument(format!(
672 "buffer size {} does not match size={}",
673 x.size(),
674 size
675 )));
676 }
677 if size == 0 {
678 return Ok(DeviceBuffer::new(0, device_id));
679 }
680
681 let max_val = x.data.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
682 let exps: Vec<f32> = x.data.iter().map(|&v| (v - max_val).exp()).collect();
683 let sum: f32 = exps.iter().sum();
684
685 let result: Vec<f32> = if sum == 0.0 {
686 vec![1.0 / size as f32; size]
687 } else {
688 exps.iter().map(|&e| e / sum).collect()
689 };
690
691 Ok(DeviceBuffer::from_slice(&result, device_id))
692 }
693
694 fn synchronize(&self, _device_id: usize) -> Result<(), GpuError> {
695 Ok(())
696 }
697
698 fn memory_info(&self, _device_id: usize) -> Result<(usize, usize), GpuError> {
699 let total = self.simulated_memory_bytes;
700 let free = total / 2;
701 Ok((free, total))
702 }
703}
704
705#[cfg(feature = "cuda")]
714pub struct CudaBackend {
715 pub device_count: usize,
717 cpu_fallback: CpuBackend,
718}
719
720#[cfg(feature = "cuda")]
721impl CudaBackend {
722 pub fn new() -> Result<Self, GpuError> {
724 warn!("CudaBackend: CUDA stub active — no real GPU acceleration");
725 Ok(Self {
726 device_count: 1,
727 cpu_fallback: CpuBackend::new(),
728 })
729 }
730}
731
732#[cfg(feature = "cuda")]
733impl GpuBackendTrait for CudaBackend {
734 fn name(&self) -> &'static str {
735 "cuda"
736 }
737
738 fn is_accelerated(&self) -> bool {
739 false
740 }
741
742 fn device_count(&self) -> usize {
743 self.device_count
744 }
745
746 fn alloc(&self, size: usize, device_id: usize) -> Result<DeviceBuffer, GpuError> {
747 warn!("CudaBackend::alloc delegating to CPU fallback");
748 self.cpu_fallback.alloc(size, device_id)
749 }
750
751 fn host_to_device(&self, src: &[f32], device_id: usize) -> Result<DeviceBuffer, GpuError> {
752 warn!("CudaBackend::host_to_device delegating to CPU fallback");
753 self.cpu_fallback.host_to_device(src, device_id)
754 }
755
756 fn device_to_host(&self, buf: &DeviceBuffer) -> Result<Vec<f32>, GpuError> {
757 warn!("CudaBackend::device_to_host delegating to CPU fallback");
758 self.cpu_fallback.device_to_host(buf)
759 }
760
761 fn matvec(
762 &self,
763 a: &DeviceBuffer,
764 x: &DeviceBuffer,
765 m: usize,
766 k: usize,
767 device_id: usize,
768 ) -> Result<DeviceBuffer, GpuError> {
769 warn!("CudaBackend::matvec delegating to CPU fallback");
770 self.cpu_fallback.matvec(a, x, m, k, device_id)
771 }
772
773 fn relu(&self, x: &DeviceBuffer, device_id: usize) -> Result<DeviceBuffer, GpuError> {
774 warn!("CudaBackend::relu delegating to CPU fallback");
775 self.cpu_fallback.relu(x, device_id)
776 }
777
778 fn softmax(
779 &self,
780 x: &DeviceBuffer,
781 size: usize,
782 device_id: usize,
783 ) -> Result<DeviceBuffer, GpuError> {
784 warn!("CudaBackend::softmax delegating to CPU fallback");
785 self.cpu_fallback.softmax(x, size, device_id)
786 }
787
788 fn synchronize(&self, device_id: usize) -> Result<(), GpuError> {
789 warn!("CudaBackend::synchronize delegating to CPU fallback");
790 self.cpu_fallback.synchronize(device_id)
791 }
792
793 fn memory_info(&self, device_id: usize) -> Result<(usize, usize), GpuError> {
794 warn!("CudaBackend::memory_info delegating to CPU fallback");
795 self.cpu_fallback.memory_info(device_id)
796 }
797}
798
799#[cfg(all(feature = "metal", target_os = "macos"))]
807pub struct MetalBackend {
808 pub device_count: usize,
810 cpu_fallback: CpuBackend,
811}
812
813#[cfg(all(feature = "metal", target_os = "macos"))]
814impl MetalBackend {
815 pub fn new() -> Result<Self, GpuError> {
817 warn!("MetalBackend: Metal stub active — no real GPU acceleration");
818 Ok(Self {
819 device_count: 1,
820 cpu_fallback: CpuBackend::new(),
821 })
822 }
823}
824
825#[cfg(all(feature = "metal", target_os = "macos"))]
826impl GpuBackendTrait for MetalBackend {
827 fn name(&self) -> &'static str {
828 "metal"
829 }
830
831 fn is_accelerated(&self) -> bool {
832 false
833 }
834
835 fn device_count(&self) -> usize {
836 self.device_count
837 }
838
839 fn alloc(&self, size: usize, device_id: usize) -> Result<DeviceBuffer, GpuError> {
840 warn!("MetalBackend::alloc delegating to CPU fallback");
841 self.cpu_fallback.alloc(size, device_id)
842 }
843
844 fn host_to_device(&self, src: &[f32], device_id: usize) -> Result<DeviceBuffer, GpuError> {
845 warn!("MetalBackend::host_to_device delegating to CPU fallback");
846 self.cpu_fallback.host_to_device(src, device_id)
847 }
848
849 fn device_to_host(&self, buf: &DeviceBuffer) -> Result<Vec<f32>, GpuError> {
850 warn!("MetalBackend::device_to_host delegating to CPU fallback");
851 self.cpu_fallback.device_to_host(buf)
852 }
853
854 fn matvec(
855 &self,
856 a: &DeviceBuffer,
857 x: &DeviceBuffer,
858 m: usize,
859 k: usize,
860 device_id: usize,
861 ) -> Result<DeviceBuffer, GpuError> {
862 warn!("MetalBackend::matvec delegating to CPU fallback");
863 self.cpu_fallback.matvec(a, x, m, k, device_id)
864 }
865
866 fn relu(&self, x: &DeviceBuffer, device_id: usize) -> Result<DeviceBuffer, GpuError> {
867 warn!("MetalBackend::relu delegating to CPU fallback");
868 self.cpu_fallback.relu(x, device_id)
869 }
870
871 fn softmax(
872 &self,
873 x: &DeviceBuffer,
874 size: usize,
875 device_id: usize,
876 ) -> Result<DeviceBuffer, GpuError> {
877 warn!("MetalBackend::softmax delegating to CPU fallback");
878 self.cpu_fallback.softmax(x, size, device_id)
879 }
880
881 fn synchronize(&self, device_id: usize) -> Result<(), GpuError> {
882 warn!("MetalBackend::synchronize delegating to CPU fallback");
883 self.cpu_fallback.synchronize(device_id)
884 }
885
886 fn memory_info(&self, device_id: usize) -> Result<(usize, usize), GpuError> {
887 warn!("MetalBackend::memory_info delegating to CPU fallback");
888 self.cpu_fallback.memory_info(device_id)
889 }
890}
891
892#[cfg(feature = "gpu")]
901pub(crate) struct Scirs2BackendHandle(pub(crate) std::sync::Arc<Scirs2Backend>);
902
903#[cfg(feature = "gpu")]
904impl GpuBackendTrait for Scirs2BackendHandle {
905 fn name(&self) -> &'static str {
906 self.0.name()
907 }
908 fn is_accelerated(&self) -> bool {
909 self.0.is_accelerated()
910 }
911 fn device_count(&self) -> usize {
912 self.0.device_count()
913 }
914 fn alloc(&self, size: usize, device_id: usize) -> Result<DeviceBuffer, GpuError> {
915 self.0.alloc(size, device_id)
916 }
917 fn host_to_device(&self, src: &[f32], device_id: usize) -> Result<DeviceBuffer, GpuError> {
918 self.0.host_to_device(src, device_id)
919 }
920 fn device_to_host(&self, buf: &DeviceBuffer) -> Result<Vec<f32>, GpuError> {
921 self.0.device_to_host(buf)
922 }
923 fn matvec(
924 &self,
925 a: &DeviceBuffer,
926 x: &DeviceBuffer,
927 m: usize,
928 k: usize,
929 device_id: usize,
930 ) -> Result<DeviceBuffer, GpuError> {
931 self.0.matvec(a, x, m, k, device_id)
932 }
933 fn relu(&self, x: &DeviceBuffer, device_id: usize) -> Result<DeviceBuffer, GpuError> {
934 self.0.relu(x, device_id)
935 }
936 fn softmax(
937 &self,
938 x: &DeviceBuffer,
939 size: usize,
940 device_id: usize,
941 ) -> Result<DeviceBuffer, GpuError> {
942 self.0.softmax(x, size, device_id)
943 }
944 fn synchronize(&self, device_id: usize) -> Result<(), GpuError> {
945 self.0.synchronize(device_id)
946 }
947 fn memory_info(&self, device_id: usize) -> Result<(usize, usize), GpuError> {
948 self.0.memory_info(device_id)
949 }
950 fn gemv_q1_g128(
951 &self,
952 block_bytes: &[u8],
953 input: &[f32],
954 n_rows: usize,
955 k: usize,
956 ) -> Result<Vec<f32>, GpuError> {
957 self.0.gemv_q1_g128(block_bytes, input, n_rows, k)
958 }
959 fn gemm_q1_g128(
960 &self,
961 block_bytes: &[u8],
962 input: &[f32],
963 m: usize,
964 n_rows: usize,
965 k: usize,
966 ) -> Result<Vec<f32>, GpuError> {
967 self.0.gemm_q1_g128(block_bytes, input, m, n_rows, k)
968 }
969 fn upload_weights_raw(
970 &self,
971 block_bytes: &[u8],
972 ) -> Result<crate::weight_cache::GpuWeightHandle, GpuError> {
973 self.0.upload_weights(block_bytes)
974 }
975 fn gemv_q1_g128_cached(
976 &self,
977 handle: crate::weight_cache::GpuWeightHandle,
978 input: &[f32],
979 n_rows: usize,
980 k: usize,
981 ) -> Result<Vec<f32>, GpuError> {
982 self.0.gemv_q1_g128_cached(handle, input, n_rows, k)
983 }
984
985 fn upload_weights_ternary(
986 &self,
987 blocks: &[pictor_core::BlockTQ2_0_g128],
988 ) -> Result<crate::weight_cache::GpuWeightHandle, GpuError> {
989 self.0.upload_weights_ternary(blocks)
990 }
991
992 fn gemv_tq2_g128_cached(
993 &self,
994 handle: crate::weight_cache::GpuWeightHandle,
995 input: &[f32],
996 n_rows: usize,
997 k: usize,
998 ) -> Result<Vec<f32>, GpuError> {
999 self.0.gemv_tq2_g128_cached(handle, input, n_rows, k)
1000 }
1001
1002 fn batch_attn_phase(
1003 &self,
1004 hidden: &[f32],
1005 norm_weight: &[f32],
1006 norm_eps: f32,
1007 qkv_handle: crate::weight_cache::GpuWeightHandle,
1008 q_rows: usize,
1009 k_rows: usize,
1010 h: usize,
1011 ) -> Result<Option<(Vec<f32>, Vec<f32>, Vec<f32>)>, GpuError> {
1012 match self
1013 .0
1014 .batch_attn_phase(hidden, norm_weight, norm_eps, qkv_handle, q_rows, k_rows, h)
1015 {
1016 Ok(result) => Ok(Some(result)),
1017 Err(e) => {
1018 tracing::warn!(error = %e, "batch_attn_phase failed, falling back");
1019 Ok(None)
1020 }
1021 }
1022 }
1023
1024 fn batch_ffn_phase(
1025 &self,
1026 hidden: &mut [f32],
1027 attn_out: &[f32],
1028 norm_weight: &[f32],
1029 norm_eps: f32,
1030 attn_proj_handle: crate::weight_cache::GpuWeightHandle,
1031 gate_up_handle: crate::weight_cache::GpuWeightHandle,
1032 down_handle: crate::weight_cache::GpuWeightHandle,
1033 h: usize,
1034 intermediate: usize,
1035 attn_proj_k: usize,
1036 ) -> Result<bool, GpuError> {
1037 match self.0.batch_ffn_phase(
1038 hidden,
1039 attn_out,
1040 norm_weight,
1041 norm_eps,
1042 attn_proj_handle,
1043 gate_up_handle,
1044 down_handle,
1045 h,
1046 intermediate,
1047 attn_proj_k,
1048 ) {
1049 Ok(()) => Ok(true),
1050 Err(e) => {
1051 tracing::warn!(error = %e, "batch_ffn_phase failed, falling back");
1052 Ok(false)
1053 }
1054 }
1055 }
1056}
1057
1058pub fn select_backend() -> Box<dyn GpuBackendTrait> {
1074 #[cfg(feature = "gpu")]
1079 use std::sync::atomic::{AtomicBool, Ordering};
1080 #[cfg(feature = "gpu")]
1081 fn warn_once(flag: &AtomicBool, msg: impl FnOnce()) {
1082 if !flag.swap(true, Ordering::Relaxed) {
1083 msg();
1084 }
1085 }
1086
1087 #[cfg(feature = "gpu")]
1089 {
1090 static SCIRS2_NOT_ACCEL: AtomicBool = AtomicBool::new(false);
1091 static SCIRS2_INIT_FAIL: AtomicBool = AtomicBool::new(false);
1092 match Scirs2Backend::global() {
1093 Ok(b) => {
1094 if b.is_accelerated() {
1095 return Box::new(Scirs2BackendHandle(b));
1096 }
1097 warn_once(&SCIRS2_NOT_ACCEL, || {
1099 warn!(
1100 "select_backend: Scirs2Backend is not accelerated (backend={}), trying next",
1101 b.backend_name()
1102 );
1103 });
1104 }
1105 Err(e) => {
1106 warn_once(&SCIRS2_INIT_FAIL, || {
1107 warn!("select_backend: Scirs2Backend init failed ({e}), trying next");
1108 });
1109 }
1110 }
1111 }
1112
1113 #[cfg(all(
1115 feature = "native-cuda",
1116 any(target_os = "linux", target_os = "windows")
1117 ))]
1118 {
1119 match NativeCudaBackend::new() {
1120 Ok(b) => {
1121 tracing::info!("select_backend: NativeCudaBackend initialised");
1122 return Box::new(b);
1123 }
1124 Err(e) => {
1125 warn!("select_backend: NativeCudaBackend init failed ({e}), trying next");
1126 }
1127 }
1128 }
1129
1130 #[cfg(feature = "cuda")]
1132 {
1133 match CudaBackend::new() {
1134 Ok(b) => {
1135 return Box::new(b);
1136 }
1137 Err(e) => {
1138 warn!("select_backend: CUDA init failed ({e}), trying next");
1139 }
1140 }
1141 }
1142
1143 #[cfg(all(feature = "metal", target_os = "macos"))]
1145 {
1146 match MetalBackend::new() {
1147 Ok(b) => {
1148 return Box::new(b);
1149 }
1150 Err(e) => {
1151 warn!("select_backend: Metal init failed ({e}), trying CPU");
1152 }
1153 }
1154 }
1155
1156 Box::new(CpuBackend::new())
1158}
1159
1160pub fn gpu_matmul(
1174 backend: &dyn GpuBackendTrait,
1175 a: &[f32],
1176 b: &[f32],
1177 m: usize,
1178 k: usize,
1179 n: usize,
1180 device_id: usize,
1181) -> Result<Vec<f32>, GpuError> {
1182 if a.len() != m * k {
1183 return Err(GpuError::InvalidArgument(format!(
1184 "a.len()={} does not match m={} k={}",
1185 a.len(),
1186 m,
1187 k
1188 )));
1189 }
1190 if b.len() != k * n {
1191 return Err(GpuError::InvalidArgument(format!(
1192 "b.len()={} does not match k={} n={}",
1193 b.len(),
1194 k,
1195 n
1196 )));
1197 }
1198
1199 let a_buf = backend.host_to_device(a, device_id)?;
1200
1201 let mut c = vec![0.0_f32; m * n];
1202
1203 for col in 0..n {
1204 let b_col: Vec<f32> = (0..k).map(|row| b[row * n + col]).collect();
1205 let x_buf = backend.host_to_device(&b_col, device_id)?;
1206 let y_buf = backend.matvec(&a_buf, &x_buf, m, k, device_id)?;
1207 let y = backend.device_to_host(&y_buf)?;
1208
1209 for row in 0..m {
1210 c[row * n + col] = y[row];
1211 }
1212 }
1213
1214 backend.synchronize(device_id)?;
1215 Ok(c)
1216}
1217
1218pub fn gpu_gemv_1bit(
1241 block_bytes: &[u8],
1242 input: &[f32],
1243 n_rows: usize,
1244 k: usize,
1245) -> Result<Vec<f32>, GpuError> {
1246 #[cfg(feature = "gpu")]
1247 {
1248 match Scirs2Backend::global() {
1249 Ok(backend) => {
1250 if backend.is_accelerated() {
1251 return backend.gemv_q1_g128(block_bytes, input, n_rows, k);
1252 }
1253 }
1255 Err(e) => {
1256 warn!("gpu_gemv_1bit: GPU init failed ({e}), using CPU fallback");
1257 }
1258 }
1259 }
1260
1261 cpu_gemv_1bit_fallback(block_bytes, input, n_rows, k)
1263}
1264
1265fn cpu_gemv_1bit_fallback(
1269 block_bytes: &[u8],
1270 input: &[f32],
1271 n_rows: usize,
1272 k: usize,
1273) -> Result<Vec<f32>, GpuError> {
1274 if k == 0 || k % 128 != 0 {
1275 return Err(GpuError::InvalidArgument(format!(
1276 "k={k} must be a positive multiple of 128"
1277 )));
1278 }
1279 if input.len() != k {
1280 return Err(GpuError::InvalidArgument(format!(
1281 "input.len()={} != k={}",
1282 input.len(),
1283 k
1284 )));
1285 }
1286 let blocks_per_row = k / 128;
1287 let block_size = 18_usize;
1288 let expected = n_rows * blocks_per_row * block_size;
1289 if block_bytes.len() < expected {
1290 return Err(GpuError::InvalidArgument(format!(
1291 "block_bytes too small: {} < {}",
1292 block_bytes.len(),
1293 expected,
1294 )));
1295 }
1296
1297 let mut output = vec![0.0_f32; n_rows];
1298
1299 for (row, output_val) in output.iter_mut().enumerate().take(n_rows) {
1300 let mut sum = 0.0_f32;
1301 for b in 0..blocks_per_row {
1302 let block_idx = row * blocks_per_row + b;
1303 let off = block_idx * block_size;
1304
1305 let d_bits = u16::from_le_bytes([block_bytes[off], block_bytes[off + 1]]);
1307 let scale = half::f16::from_bits(d_bits).to_f32();
1308
1309 let input_base = b * 128;
1310 for w in 0..4_usize {
1312 let byte_off = off + 2 + w * 4;
1313 let bits = u32::from_le_bytes([
1314 block_bytes[byte_off],
1315 block_bytes[byte_off + 1],
1316 block_bytes[byte_off + 2],
1317 block_bytes[byte_off + 3],
1318 ]);
1319 let base = input_base + w * 32;
1320 for i in 0..32_usize {
1321 let sign = if (bits >> i) & 1 == 1 {
1322 1.0_f32
1323 } else {
1324 -1.0_f32
1325 };
1326 sum += scale * sign * input[base + i];
1327 }
1328 }
1329 }
1330 *output_val = sum;
1331 }
1332
1333 Ok(output)
1334}
1335
1336#[cfg(test)]
1341mod tests {
1342 use super::*;
1343
1344 #[test]
1345 fn device_buffer_new_zeroed() {
1346 let buf = DeviceBuffer::new(4, 0);
1347 assert_eq!(buf.size(), 4);
1348 assert_eq!(buf.device_id(), 0);
1349 assert!(buf.data.iter().all(|&v| v == 0.0));
1350 }
1351
1352 #[test]
1353 fn device_buffer_from_slice_roundtrip() {
1354 let src = [1.0_f32, 2.0, 3.0];
1355 let buf = DeviceBuffer::from_slice(&src, 1);
1356 assert_eq!(buf.to_vec(), src);
1357 }
1358
1359 #[test]
1360 fn launch_config_for_zero_elements() {
1361 let cfg = LaunchConfig::for_n_elements(0);
1362 assert_eq!(cfg.grid_dim.0, 1);
1363 }
1364
1365 #[test]
1366 fn cpu_softmax_empty() {
1367 let backend = CpuBackend::new();
1368 let buf = DeviceBuffer::new(0, 0);
1369 let out = backend.softmax(&buf, 0, 0).expect("softmax empty");
1370 assert_eq!(out.size(), 0);
1371 }
1372
1373 #[test]
1376 fn cpu_gemv_1bit_identity_scale() {
1377 let scale = half::f16::from_f32(1.0);
1379 let scale_bytes = scale.to_bits().to_le_bytes();
1380
1381 let mut block = vec![0u8; 18];
1382 block[0] = scale_bytes[0];
1383 block[1] = scale_bytes[1];
1384 block[2..18].fill(0xFF);
1386
1387 let input: Vec<f32> = (0..128).map(|i| i as f32).collect();
1388 let expected: f32 = input.iter().sum(); let result =
1391 cpu_gemv_1bit_fallback(&block, &input, 1, 128).expect("cpu_gemv_1bit_fallback");
1392 assert!(
1393 (result[0] - expected).abs() < 1e-2,
1394 "got {} expected {}",
1395 result[0],
1396 expected,
1397 );
1398 }
1399
1400 #[test]
1401 fn cpu_gemv_1bit_negative_scale() {
1402 let scale = half::f16::from_f32(1.0);
1405 let scale_bytes = scale.to_bits().to_le_bytes();
1406
1407 let mut block = vec![0u8; 18];
1408 block[0] = scale_bytes[0];
1409 block[1] = scale_bytes[1];
1410 let input = vec![1.0_f32; 128];
1413 let result =
1414 cpu_gemv_1bit_fallback(&block, &input, 1, 128).expect("cpu_gemv_1bit_fallback");
1415 assert!(
1416 (result[0] - (-128.0)).abs() < 1e-2,
1417 "got {} expected -128",
1418 result[0],
1419 );
1420 }
1421
1422 #[test]
1423 fn cpu_gemv_1bit_bad_k() {
1424 let result = cpu_gemv_1bit_fallback(&[], &[], 0, 64);
1425 assert!(result.is_err());
1426 }
1427
1428 #[test]
1429 fn gpu_gemv_1bit_without_gpu() {
1430 let scale = half::f16::from_f32(1.0);
1432 let scale_bytes = scale.to_bits().to_le_bytes();
1433
1434 let mut block = vec![0u8; 18];
1435 block[0] = scale_bytes[0];
1436 block[1] = scale_bytes[1];
1437 block[2..18].fill(0xFF);
1438
1439 let input: Vec<f32> = vec![1.0_f32; 128];
1440 let result = gpu_gemv_1bit(&block, &input, 1, 128).expect("gpu_gemv_1bit");
1441 assert!((result[0] - 128.0).abs() < 1e-2, "got {}", result[0]);
1442 }
1443}