1#[cfg(target_os = "linux")]
18mod cuda_impl {
19 use ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2, ArrayView3, Axis};
20
21 use crate::driver::{array_from_row_major, from_col_major, to_col_major, to_i32, to_row_major};
22
23 use super::super::device_runtime::GpuRuntime;
24 use cudarc::cublas::sys::{
25 cublasDiagType_t, cublasFillMode_t, cublasOperation_t, cublasSideMode_t, cublasStatus_t,
26 };
27 use cudarc::cublas::{CudaBlas, Gemm, GemmConfig, Gemv, GemvConfig, StridedBatchedConfig};
28 use cudarc::cusolver::{DnHandle, sys as cusolver_sys};
29 use cudarc::driver::{CudaSlice, CudaStream, DevicePtr, DevicePtrMut};
30 use std::sync::Arc;
31
32 #[inline]
39 pub(crate) fn stream_and_blas_for(ordinal: usize) -> Option<(Arc<CudaStream>, CudaBlas)> {
40 let stream = super::super::device_runtime::cuda_context_for(ordinal)?
41 .new_stream()
42 .ok()?;
43 let blas = CudaBlas::new(stream.clone()).ok()?;
44 Some((stream, blas))
45 }
46
47 #[inline]
48 fn stream_and_blas(runtime: &GpuRuntime) -> Option<(Arc<CudaStream>, CudaBlas)> {
49 stream_and_blas_for(runtime.device.ordinal)
50 }
51
52 #[inline]
53 fn vector_values(v: ArrayView1<'_, f64>) -> Vec<f64> {
54 v.iter().copied().collect()
55 }
56
57 #[inline]
58 fn to_col_major_batch(batch: ArrayView3<'_, f64>) -> Vec<f64> {
59 let (batch_len, rows, cols) = batch.dim();
60 let mut out = Vec::with_capacity(batch_len.saturating_mul(rows).saturating_mul(cols));
61 for matrix in batch.axis_iter(Axis(0)) {
62 out.extend(to_col_major(&matrix).iter().copied());
63 }
64 out
65 }
66
67 #[inline]
68 fn from_col_major_batch(
69 data: &[f64],
70 batch: usize,
71 rows: usize,
72 cols: usize,
73 ) -> Option<Array3<f64>> {
74 if data.len() != batch.checked_mul(rows)?.checked_mul(cols)? {
75 return None;
76 }
77 let mut out = Array3::<f64>::zeros((batch, rows, cols));
78 let matrix_len = rows.checked_mul(cols)?;
79 for batch_idx in 0..batch {
80 let base = batch_idx.checked_mul(matrix_len)?;
81 for col in 0..cols {
82 for row in 0..rows {
83 out[[batch_idx, row, col]] = data[base + col * rows + row];
84 }
85 }
86 }
87 Some(out)
88 }
89
90 #[inline]
91 fn row_scale_device(
92 blas: &CudaBlas,
93 stream: &Arc<CudaStream>,
94 matrix_dev: &CudaSlice<f64>,
95 weights_dev: &CudaSlice<f64>,
96 scaled_dev: &mut CudaSlice<f64>,
97 rows: usize,
98 cols: usize,
99 ) -> Option<()> {
100 let rows_i = to_i32(rows)?;
101 let cols_i = to_i32(cols)?;
102 let handle = *blas.handle();
103 let (matrix_ptr, _matrix_record) = matrix_dev.device_ptr(stream);
104 let (weights_ptr, _weights_record) = weights_dev.device_ptr(stream);
105 let (scaled_ptr, _scaled_record) = scaled_dev.device_ptr_mut(stream);
106 let status = unsafe {
110 cudarc::cublas::sys::cublasDdgmm(
111 handle,
112 cublasSideMode_t::CUBLAS_SIDE_LEFT,
113 rows_i,
114 cols_i,
115 matrix_ptr as *const f64,
116 rows_i,
117 weights_ptr as *const f64,
118 1,
119 scaled_ptr as *mut f64,
120 rows_i,
121 )
122 };
123 if status == cublasStatus_t::CUBLAS_STATUS_SUCCESS {
124 Some(())
125 } else {
126 None
127 }
128 }
129
130 #[inline]
131 fn weighted_crossprod(
132 runtime: &GpuRuntime,
133 left: ArrayView2<'_, f64>,
134 weights: ArrayView1<'_, f64>,
135 right: ArrayView2<'_, f64>,
136 ) -> Option<Array2<f64>> {
137 weighted_crossprod_for(runtime.device.ordinal, left, weights, right)
138 }
139
140 #[inline]
141 fn weighted_crossprod_for(
142 ordinal: usize,
143 left: ArrayView2<'_, f64>,
144 weights: ArrayView1<'_, f64>,
145 right: ArrayView2<'_, f64>,
146 ) -> Option<Array2<f64>> {
147 let (rows, left_cols) = left.dim();
148 let (right_rows, right_cols) = right.dim();
149 if rows == 0
150 || left_cols == 0
151 || right_cols == 0
152 || rows != right_rows
153 || rows != weights.len()
154 {
155 return None;
156 }
157
158 let (stream, blas) = stream_and_blas_for(ordinal)?;
159 let same_operand = std::ptr::eq(left.as_ptr(), right.as_ptr())
166 && left.dim() == right.dim()
167 && left.strides() == right.strides();
168 let left_col = to_col_major(&left);
169 let weights_host = vector_values(weights);
170 let left_dev = stream.clone_htod(&*left_col).ok()?;
171 let right_dev = if same_operand {
175 None
176 } else {
177 let right_col = to_col_major(&right);
178 Some(stream.clone_htod(&*right_col).ok()?)
179 };
180 let weights_dev = stream.clone_htod(&weights_host).ok()?;
181 let mut weighted_right_dev = stream
182 .alloc_zeros::<f64>(rows.checked_mul(right_cols)?)
183 .ok()?;
184 row_scale_device(
185 &blas,
186 &stream,
187 right_dev.as_ref().unwrap_or(&left_dev),
188 &weights_dev,
189 &mut weighted_right_dev,
190 rows,
191 right_cols,
192 )?;
193
194 let mut out_dev = stream
195 .alloc_zeros::<f64>(left_cols.checked_mul(right_cols)?)
196 .ok()?;
197 let cfg = GemmConfig::<f64> {
198 transa: cublasOperation_t::CUBLAS_OP_T,
199 transb: cublasOperation_t::CUBLAS_OP_N,
200 m: to_i32(left_cols)?,
201 n: to_i32(right_cols)?,
202 k: to_i32(rows)?,
203 alpha: 1.0,
204 lda: to_i32(rows)?,
205 ldb: to_i32(rows)?,
206 beta: 0.0,
207 ldc: to_i32(left_cols)?,
208 };
209 unsafe { blas.gemm(cfg, &left_dev, &weighted_right_dev, &mut out_dev) }.ok()?;
212 let out_col = stream.clone_dtoh(&out_dev).ok()?;
213 from_col_major(&out_col, left_cols, right_cols)
214 }
215
216 pub(crate) struct ResidentWeightedGram {
231 stream: Arc<CudaStream>,
232 blas: CudaBlas,
233 x_dev: CudaSlice<f64>,
234 rows: usize,
235 cols: usize,
236 }
237
238 impl ResidentWeightedGram {
239 pub(crate) fn new(ordinal: usize, x: ArrayView2<'_, f64>) -> Option<Self> {
243 let (rows, cols) = x.dim();
244 if rows == 0 || cols == 0 {
245 return None;
246 }
247 let (stream, blas) = stream_and_blas_for(ordinal)?;
248 let x_col = to_col_major(&x);
249 let x_dev = stream.clone_htod(&*x_col).ok()?;
250 Some(Self {
251 stream,
252 blas,
253 x_dev,
254 rows,
255 cols,
256 })
257 }
258
259 #[inline]
260 pub(crate) fn dims(&self) -> (usize, usize) {
261 (self.rows, self.cols)
262 }
263
264 pub(crate) fn gram(&self, w: ArrayView1<'_, f64>) -> Option<Array2<f64>> {
269 if w.len() != self.rows {
270 return None;
271 }
272 let weights_host = vector_values(w);
273 let weights_dev = self.stream.clone_htod(&weights_host).ok()?;
274 let mut weighted_dev = self
275 .stream
276 .alloc_zeros::<f64>(self.rows.checked_mul(self.cols)?)
277 .ok()?;
278 row_scale_device(
279 &self.blas,
280 &self.stream,
281 &self.x_dev,
282 &weights_dev,
283 &mut weighted_dev,
284 self.rows,
285 self.cols,
286 )?;
287 let mut out_dev = self
288 .stream
289 .alloc_zeros::<f64>(self.cols.checked_mul(self.cols)?)
290 .ok()?;
291 let cfg = GemmConfig::<f64> {
292 transa: cublasOperation_t::CUBLAS_OP_T,
293 transb: cublasOperation_t::CUBLAS_OP_N,
294 m: to_i32(self.cols)?,
295 n: to_i32(self.cols)?,
296 k: to_i32(self.rows)?,
297 alpha: 1.0,
298 lda: to_i32(self.rows)?,
299 ldb: to_i32(self.rows)?,
300 beta: 0.0,
301 ldc: to_i32(self.cols)?,
302 };
303 unsafe {
306 self.blas
307 .gemm(cfg, &self.x_dev, &weighted_dev, &mut out_dev)
308 }
309 .ok()?;
310 let out_col = self.stream.clone_dtoh(&out_dev).ok()?;
311 from_col_major(&out_col, self.cols, self.cols)
312 }
313
314 pub(crate) fn solve_psd_normal_equations(
333 &self,
334 w: ArrayView1<'_, f64>,
335 rhs: ArrayView1<'_, f64>,
336 ridge: f64,
337 ) -> Option<Array1<f64>> {
338 if w.len() != self.rows || rhs.len() != self.cols {
339 return None;
340 }
341 let p = self.cols;
342
343 let weights_dev = self.stream.clone_htod(&vector_values(w)).ok()?;
345 let mut weighted_dev = self
346 .stream
347 .alloc_zeros::<f64>(self.rows.checked_mul(p)?)
348 .ok()?;
349 row_scale_device(
350 &self.blas,
351 &self.stream,
352 &self.x_dev,
353 &weights_dev,
354 &mut weighted_dev,
355 self.rows,
356 p,
357 )?;
358
359 let mut ridge_init = vec![0.0_f64; p.checked_mul(p)?];
366 for i in 0..p {
367 ridge_init[i * p + i] = ridge;
368 }
369 let mut g_dev = self.stream.clone_htod(&ridge_init).ok()?;
370 let cfg = GemmConfig::<f64> {
371 transa: cublasOperation_t::CUBLAS_OP_T,
372 transb: cublasOperation_t::CUBLAS_OP_N,
373 m: to_i32(p)?,
374 n: to_i32(p)?,
375 k: to_i32(self.rows)?,
376 alpha: 1.0,
377 lda: to_i32(self.rows)?,
378 ldb: to_i32(self.rows)?,
379 beta: 1.0,
381 ldc: to_i32(p)?,
382 };
383 unsafe { self.blas.gemm(cfg, &self.x_dev, &weighted_dev, &mut g_dev) }.ok()?;
386
387 let solver = DnHandle::new(self.stream.clone()).ok()?;
389 let info = potrf_single_dev(&solver, &self.stream, p, &mut g_dev)?;
390 if info != 0 {
391 return None;
393 }
394
395 let mut rhs_dev = self.stream.clone_htod(&vector_values(rhs)).ok()?;
397 trsm_single_vec(&self.blas, &self.stream, p, &g_dev, &mut rhs_dev, false)?; trsm_single_vec(&self.blas, &self.stream, p, &g_dev, &mut rhs_dev, true)?; let beta_host = self.stream.clone_dtoh(&rhs_dev).ok()?;
402 Some(Array1::from_vec(beta_host))
403 }
404 }
405
406 fn potrf_single_dev(
410 solver: &DnHandle,
411 stream: &Arc<CudaStream>,
412 p: usize,
413 matrix: &mut CudaSlice<f64>,
414 ) -> Option<i32> {
415 let p_i = to_i32(p)?;
416 let uplo = cusolver_sys::cublasFillMode_t::CUBLAS_FILL_MODE_LOWER;
417 let mut lwork = 0_i32;
418 {
419 let (mat_ptr, _rec) = matrix.device_ptr_mut(stream);
420 let status = unsafe {
422 cusolver_sys::cusolverDnDpotrf_bufferSize(
423 solver.cu(),
424 uplo,
425 p_i,
426 mat_ptr as *mut f64,
427 p_i,
428 &mut lwork,
429 )
430 };
431 if status != cusolver_sys::cusolverStatus_t::CUSOLVER_STATUS_SUCCESS {
432 return None;
433 }
434 }
435 let mut workspace = stream.alloc_zeros::<f64>(lwork.max(1) as usize).ok()?;
436 let mut info_dev = stream.alloc_zeros::<i32>(1).ok()?;
437 {
438 let (mat_ptr, _rec) = matrix.device_ptr_mut(stream);
439 let (work_ptr, _wrec) = workspace.device_ptr_mut(stream);
440 let (info_ptr, _irec) = info_dev.device_ptr_mut(stream);
441 let status = unsafe {
443 cusolver_sys::cusolverDnDpotrf(
444 solver.cu(),
445 uplo,
446 p_i,
447 mat_ptr as *mut f64,
448 p_i,
449 work_ptr as *mut f64,
450 lwork,
451 info_ptr as *mut i32,
452 )
453 };
454 if status != cusolver_sys::cusolverStatus_t::CUSOLVER_STATUS_SUCCESS {
455 return None;
456 }
457 }
458 let info_host = stream.clone_dtoh(&info_dev).ok()?;
459 info_host.first().copied()
460 }
461
462 fn trsm_single_vec(
466 blas: &CudaBlas,
467 stream: &Arc<CudaStream>,
468 p: usize,
469 l: &CudaSlice<f64>,
470 rhs: &mut CudaSlice<f64>,
471 transposed: bool,
472 ) -> Option<()> {
473 let alpha = 1.0_f64;
474 let p_i = to_i32(p)?;
475 let handle = *blas.handle();
476 let (l_ptr, _l_rec) = l.device_ptr(stream);
477 let (rhs_ptr, _rhs_rec) = rhs.device_ptr_mut(stream);
478 let status = unsafe {
480 cudarc::cublas::sys::cublasDtrsm_v2(
481 handle,
482 cublasSideMode_t::CUBLAS_SIDE_LEFT,
483 cublasFillMode_t::CUBLAS_FILL_MODE_LOWER,
484 if transposed {
485 cublasOperation_t::CUBLAS_OP_T
486 } else {
487 cublasOperation_t::CUBLAS_OP_N
488 },
489 cublasDiagType_t::CUBLAS_DIAG_NON_UNIT,
490 p_i,
491 1,
492 &alpha,
493 l_ptr as *const f64,
494 p_i,
495 rhs_ptr as *mut f64,
496 p_i,
497 )
498 };
499 if status == cublasStatus_t::CUBLAS_STATUS_SUCCESS {
500 Some(())
501 } else {
502 None
503 }
504 }
505
506 #[inline]
507 fn assign_block(
508 out: &mut Array2<f64>,
509 row_offset: usize,
510 col_offset: usize,
511 block: &Array2<f64>,
512 ) {
513 let (rows, cols) = block.dim();
514 for col in 0..cols {
515 for row in 0..rows {
516 out[[row_offset + row, col_offset + col]] = block[[row, col]];
517 }
518 }
519 }
520
521 #[inline]
522 fn mirror_upper_to_lower(out: &mut Array2<f64>) {
523 let n = out.nrows();
524 for row in 0..n {
525 for col in 0..row {
526 out[[row, col]] = out[[col, row]];
527 }
528 }
529 }
530
531 #[inline]
532 pub(crate) fn gemm_cuda(
533 runtime: &GpuRuntime,
534 a: ArrayView2<'_, f64>,
535 b: ArrayView2<'_, f64>,
536 trans_a: bool,
537 trans_b: bool,
538 ) -> Option<Array2<f64>> {
539 gemm_on_ordinal_cuda(runtime.device.ordinal, a, b, trans_a, trans_b)
540 }
541
542 #[inline]
548 pub(crate) fn gemm_on_ordinal_cuda(
549 ordinal: usize,
550 a: ArrayView2<'_, f64>,
551 b: ArrayView2<'_, f64>,
552 trans_a: bool,
553 trans_b: bool,
554 ) -> Option<Array2<f64>> {
555 let (a_rows, a_cols) = a.dim();
556 let (b_rows, b_cols) = b.dim();
557 let (m, k_a) = if trans_a {
558 (a_cols, a_rows)
559 } else {
560 (a_rows, a_cols)
561 };
562 let (k_b, n) = if trans_b {
563 (b_cols, b_rows)
564 } else {
565 (b_rows, b_cols)
566 };
567 if m == 0 || n == 0 || k_a == 0 || k_a != k_b {
568 return None;
569 }
570 let (stream, blas) = stream_and_blas_for(ordinal)?;
571 let b_rm = to_row_major(&b);
593 let a_rm = to_row_major(&a);
594 let x_dev = stream.clone_htod(&*b_rm).ok()?;
595 let y_dev = stream.clone_htod(&*a_rm).ok()?;
596 let mut out_dev = stream.alloc_zeros::<f64>(m.checked_mul(n)?).ok()?;
597 let cfg = GemmConfig::<f64> {
598 transa: if trans_b {
599 cublasOperation_t::CUBLAS_OP_T
600 } else {
601 cublasOperation_t::CUBLAS_OP_N
602 },
603 transb: if trans_a {
604 cublasOperation_t::CUBLAS_OP_T
605 } else {
606 cublasOperation_t::CUBLAS_OP_N
607 },
608 m: to_i32(n)?,
609 n: to_i32(m)?,
610 k: to_i32(k_a)?,
611 alpha: 1.0,
612 lda: to_i32(b_cols)?,
615 ldb: to_i32(a_cols)?,
616 beta: 0.0,
617 ldc: to_i32(n)?,
618 };
619 unsafe { blas.gemm(cfg, &x_dev, &y_dev, &mut out_dev) }.ok()?;
622 let out_rm = stream.clone_dtoh(&out_dev).ok()?;
624 array_from_row_major(out_rm, m, n)
625 }
626
627 #[inline]
632 pub(crate) fn gemm_broadcast_b_batched_cuda(
633 ordinal: usize,
634 a: ArrayView3<'_, f64>,
635 b: ArrayView2<'_, f64>,
636 ) -> Option<Array3<f64>> {
637 let (batch, m, k) = a.dim();
638 let (b_rows, n) = b.dim();
639 if batch == 0 || m == 0 || n == 0 || k == 0 || b_rows != k {
640 return None;
641 }
642 let (stream, blas) = stream_and_blas_for(ordinal)?;
643 let a_col = to_col_major_batch(a);
644 let b_col = to_col_major(&b);
645 let a_dev = stream.clone_htod(&a_col).ok()?;
646 let b_dev = stream.clone_htod(&*b_col).ok()?;
647 let mut out_dev = stream
648 .alloc_zeros::<f64>(batch.checked_mul(m)?.checked_mul(n)?)
649 .ok()?;
650 let cfg = StridedBatchedConfig::<f64> {
651 gemm: GemmConfig::<f64> {
652 transa: cublasOperation_t::CUBLAS_OP_N,
653 transb: cublasOperation_t::CUBLAS_OP_N,
654 m: to_i32(m)?,
655 n: to_i32(n)?,
656 k: to_i32(k)?,
657 alpha: 1.0,
658 lda: to_i32(m)?,
659 ldb: to_i32(k)?,
660 beta: 0.0,
661 ldc: to_i32(m)?,
662 },
663 batch_size: to_i32(batch)?,
664 stride_a: i64::try_from(m.checked_mul(k)?).ok()?,
665 stride_b: 0,
666 stride_c: i64::try_from(m.checked_mul(n)?).ok()?,
667 };
668 unsafe { blas.gemm_strided_batched(cfg, &a_dev, &b_dev, &mut out_dev) }.ok()?;
672 let out_col = stream.clone_dtoh(&out_dev).ok()?;
673 from_col_major_batch(&out_col, batch, m, n)
674 }
675
676 #[inline]
680 pub(crate) fn gemm_abt_strided_batched_cuda(
681 ordinal: usize,
682 a: ArrayView3<'_, f64>,
683 b: ArrayView3<'_, f64>,
684 ) -> Option<Array3<f64>> {
685 let (batch, m, k) = a.dim();
686 let (batch_b, n, k_b) = b.dim();
687 if batch == 0 || m == 0 || n == 0 || k == 0 || batch != batch_b || k != k_b {
688 return None;
689 }
690 let (stream, blas) = stream_and_blas_for(ordinal)?;
691 let a_col = to_col_major_batch(a);
692 let b_col = to_col_major_batch(b);
693 let a_dev = stream.clone_htod(&a_col).ok()?;
694 let b_dev = stream.clone_htod(&b_col).ok()?;
695 let mut out_dev = stream
696 .alloc_zeros::<f64>(batch.checked_mul(m)?.checked_mul(n)?)
697 .ok()?;
698 let cfg = StridedBatchedConfig::<f64> {
699 gemm: GemmConfig::<f64> {
700 transa: cublasOperation_t::CUBLAS_OP_N,
701 transb: cublasOperation_t::CUBLAS_OP_T,
702 m: to_i32(m)?,
703 n: to_i32(n)?,
704 k: to_i32(k)?,
705 alpha: 1.0,
706 lda: to_i32(m)?,
707 ldb: to_i32(n)?,
708 beta: 0.0,
709 ldc: to_i32(m)?,
710 },
711 batch_size: to_i32(batch)?,
712 stride_a: i64::try_from(m.checked_mul(k)?).ok()?,
713 stride_b: i64::try_from(n.checked_mul(k)?).ok()?,
714 stride_c: i64::try_from(m.checked_mul(n)?).ok()?,
715 };
716 unsafe { blas.gemm_strided_batched(cfg, &a_dev, &b_dev, &mut out_dev) }.ok()?;
719 let out_col = stream.clone_dtoh(&out_dev).ok()?;
720 from_col_major_batch(&out_col, batch, m, n)
721 }
722
723 #[inline]
724 pub(crate) fn gemv_cuda(
725 runtime: &GpuRuntime,
726 a: ArrayView2<'_, f64>,
727 v: ArrayView1<'_, f64>,
728 trans_a: bool,
729 ) -> Option<Array1<f64>> {
730 let (rows, cols) = a.dim();
731 let out_len = if trans_a { cols } else { rows };
732 let needed = if trans_a { rows } else { cols };
733 if out_len == 0 || needed == 0 || v.len() != needed {
734 return None;
735 }
736 let (stream, blas) = stream_and_blas(runtime)?;
737 let a_col = to_col_major(&a);
738 let a_dev = stream.clone_htod(&*a_col).ok()?;
739 let v_host = vector_values(v);
740 let v_dev = stream.clone_htod(&v_host).ok()?;
741 let mut out_dev = stream.alloc_zeros::<f64>(out_len).ok()?;
742 let cfg = GemvConfig::<f64> {
743 trans: if trans_a {
744 cublasOperation_t::CUBLAS_OP_T
745 } else {
746 cublasOperation_t::CUBLAS_OP_N
747 },
748 m: to_i32(rows)?,
749 n: to_i32(cols)?,
750 alpha: 1.0,
751 lda: to_i32(rows)?,
752 incx: 1,
753 beta: 0.0,
754 incy: 1,
755 };
756 unsafe { blas.gemv(cfg, &a_dev, &v_dev, &mut out_dev) }.ok()?;
758 Some(Array1::from_vec(stream.clone_dtoh(&out_dev).ok()?))
759 }
760
761 #[inline]
762 pub fn xt_diag_x_cuda(
763 runtime: &GpuRuntime,
764 x: ArrayView2<'_, f64>,
765 w: ArrayView1<'_, f64>,
766 ) -> Option<Array2<f64>> {
767 let (rows, cols) = x.dim();
768 if rows == 0 || cols == 0 || rows != w.len() {
769 return None;
770 }
771 weighted_crossprod(runtime, x, w, x)
772 }
773
774 #[inline]
775 pub(crate) fn xt_diag_x_on_ordinal_cuda(
776 ordinal: usize,
777 x: ArrayView2<'_, f64>,
778 w: ArrayView1<'_, f64>,
779 ) -> Option<Array2<f64>> {
780 let (rows, cols) = x.dim();
781 if rows == 0 || cols == 0 || rows != w.len() {
782 return None;
783 }
784 weighted_crossprod_for(ordinal, x, w, x)
785 }
786
787 #[inline]
788 pub fn xt_diag_y_cuda(
789 runtime: &GpuRuntime,
790 x: ArrayView2<'_, f64>,
791 w: ArrayView1<'_, f64>,
792 y: ArrayView2<'_, f64>,
793 ) -> Option<Array2<f64>> {
794 weighted_crossprod(runtime, x, w, y)
795 }
796
797 #[inline]
798 pub(crate) fn joint_hessian_2x2_cuda(
799 runtime: &GpuRuntime,
800 x_a: ArrayView2<'_, f64>,
801 x_b: ArrayView2<'_, f64>,
802 w_aa: ArrayView1<'_, f64>,
803 w_ab: ArrayView1<'_, f64>,
804 w_bb: ArrayView1<'_, f64>,
805 ) -> Option<Array2<f64>> {
806 let (rows, pa) = x_a.dim();
807 let (rows_b, pb) = x_b.dim();
808 let total = pa.checked_add(pb)?;
809 if rows == 0
810 || total == 0
811 || rows != rows_b
812 || rows != w_aa.len()
813 || rows != w_ab.len()
814 || rows != w_bb.len()
815 {
816 return None;
817 }
818
819 let mut out = Array2::<f64>::zeros((total, total));
820 if pa > 0 {
821 let aa = weighted_crossprod(runtime, x_a, w_aa, x_a)?;
822 assign_block(&mut out, 0, 0, &aa);
823 }
824 if pa > 0 && pb > 0 {
825 let ab = weighted_crossprod(runtime, x_a, w_ab, x_b)?;
826 assign_block(&mut out, 0, pa, &ab);
827 }
828 if pb > 0 {
829 let bb = weighted_crossprod(runtime, x_b, w_bb, x_b)?;
830 assign_block(&mut out, pa, pa, &bb);
831 }
832 mirror_upper_to_lower(&mut out);
833 Some(out)
834 }
835
836 #[inline]
837 pub(crate) fn trsm_cuda(
838 runtime: &GpuRuntime,
839 triangular: ArrayView2<'_, f64>,
840 rhs: ArrayView2<'_, f64>,
841 upper: bool,
842 ) -> Option<Array2<f64>> {
843 let (n, n2) = triangular.dim();
844 if n == 0 || n != n2 || rhs.nrows() != n {
845 return None;
846 }
847 let nrhs = rhs.ncols();
848 let (stream, blas) = stream_and_blas(runtime)?;
849 let tri_col = to_col_major(&triangular);
850 let rhs_col = to_col_major(&rhs);
851 let tri_dev = stream.clone_htod(&*tri_col).ok()?;
852 let mut rhs_dev = stream.clone_htod(&*rhs_col).ok()?;
853 let alpha = 1.0_f64;
854 let handle = *blas.handle();
855 {
856 let (tri_ptr, _tri_record) = tri_dev.device_ptr(&stream);
857 let (rhs_ptr, _rhs_record) = rhs_dev.device_ptr_mut(&stream);
858 let status = unsafe {
861 cudarc::cublas::sys::cublasDtrsm_v2(
862 handle,
863 cublasSideMode_t::CUBLAS_SIDE_LEFT,
864 if upper {
865 cublasFillMode_t::CUBLAS_FILL_MODE_UPPER
866 } else {
867 cublasFillMode_t::CUBLAS_FILL_MODE_LOWER
868 },
869 cublasOperation_t::CUBLAS_OP_N,
870 cublasDiagType_t::CUBLAS_DIAG_NON_UNIT,
871 to_i32(n)?,
872 to_i32(nrhs)?,
873 &alpha,
874 tri_ptr as *const f64,
875 to_i32(n)?,
876 rhs_ptr as *mut f64,
877 to_i32(n)?,
878 )
879 };
880 if status != cublasStatus_t::CUBLAS_STATUS_SUCCESS {
881 return None;
882 }
883 };
884 let out_col = stream.clone_dtoh(&rhs_dev).ok()?;
885 from_col_major(&out_col, n, nrhs)
886 }
887}
888
889#[cfg(target_os = "linux")]
890pub(crate) use cuda_impl::{
891 ResidentWeightedGram, gemm_abt_strided_batched_cuda, gemm_broadcast_b_batched_cuda, gemm_cuda,
892 gemm_on_ordinal_cuda, gemv_cuda, joint_hessian_2x2_cuda, trsm_cuda, xt_diag_x_on_ordinal_cuda,
893};
894#[cfg(target_os = "linux")]
901pub use cuda_impl::{xt_diag_x_cuda, xt_diag_y_cuda};