gam_solve/gpu/
reml_gpu.rs1use ndarray::{Array1, ArrayView2};
18
19#[derive(Clone, Debug)]
20pub struct RemlGpuInput<'a> {
21 pub penalized_hessian: ArrayView2<'a, f64>,
22 pub derivative_hessians: Vec<ArrayView2<'a, f64>>,
23}
24
25#[derive(Clone, Debug)]
26pub struct RemlGpuEvidence {
27 pub logdet_hessian: f64,
28 pub gradient_rho: Array1<f64>,
29}
30
31pub fn evidence_derivatives_gpu(input: RemlGpuInput<'_>) -> Result<RemlGpuEvidence, String> {
32 let p = input.penalized_hessian.nrows();
33 if p != input.penalized_hessian.ncols() {
34 return Err("REML GPU Hessian must be square".to_string());
35 }
36 for (j, derivative) in input.derivative_hessians.iter().enumerate() {
37 if derivative.dim() != (p, p) {
38 return Err(format!(
39 "REML derivative Hessian {j} has shape {:?}, expected {p}x{p}",
40 derivative.dim()
41 ));
42 }
43 }
44
45 #[cfg(target_os = "linux")]
46 {
47 if gam_gpu::device_runtime::GpuRuntime::resolve(gam_gpu::global_policy())
48 .map_err(|error| error.to_string())?
49 .is_some()
50 {
51 return linux_cuda::evidence_derivatives(input);
52 }
53 }
54
55 cpu_fallback::evidence_derivatives(input)
56}
57
58#[cfg(target_os = "linux")]
59mod linux_cuda {
60 use super::{RemlGpuEvidence, RemlGpuInput};
61 use cudarc::cusolver::DnHandle;
62 use gam_gpu::driver::to_col_major;
63 use gam_gpu::solver::{
64 cholesky_logdet_from_col_major, context_and_stream, pinned_htod, potrf_in_place,
65 potrs_in_place,
66 };
67 use ndarray::Array1;
68
69 pub(super) fn evidence_derivatives(input: RemlGpuInput<'_>) -> Result<RemlGpuEvidence, String> {
70 let p = input.penalized_hessian.nrows();
71 let d = input.derivative_hessians.len();
72 let (_, stream) = context_and_stream()?;
73 let solver = DnHandle::new(stream.clone()).map_err(|e| format!("cusolver init: {e}"))?;
74
75 let h_col = to_col_major(&input.penalized_hessian);
77 let mut h_dev = pinned_htod(&stream, &h_col)?;
78 potrf_in_place(&solver, &stream, p, &mut h_dev)?;
79 let factor_col = stream
80 .clone_dtoh(&h_dev)
81 .map_err(|e| format!("download Cholesky factor: {e}"))?;
82 let logdet_hessian = cholesky_logdet_from_col_major(&factor_col, p);
83
84 if d == 0 {
85 return Ok(RemlGpuEvidence {
86 logdet_hessian,
87 gradient_rho: Array1::<f64>::zeros(0),
88 });
89 }
90
91 let total_cols = p
94 .checked_mul(d)
95 .ok_or_else(|| format!("REML GPU RHS width overflow: p={p}, d={d}"))?;
96 let total_elems = p
97 .checked_mul(total_cols)
98 .ok_or_else(|| format!("REML GPU RHS size overflow: p={p}, cols={total_cols}"))?;
99 let mut rhs_col = Vec::<f64>::with_capacity(total_elems);
100 for derivative in &input.derivative_hessians {
101 let col = to_col_major(derivative);
102 rhs_col.extend_from_slice(&col);
103 }
104 let mut rhs_dev = pinned_htod(&stream, &rhs_col)?;
105 potrs_in_place(&solver, &stream, p, total_cols, &h_dev, &mut rhs_dev)?;
106 let solved_col = stream
107 .clone_dtoh(&rhs_dev)
108 .map_err(|e| format!("download REML derivative solves: {e}"))?;
109
110 let mut gradient_rho = Array1::<f64>::zeros(d);
111 for j in 0..d {
112 let offset = j * p * p;
113 let mut trace = 0.0_f64;
115 for i in 0..p {
116 trace += solved_col[offset + i * p + i];
117 }
118 gradient_rho[j] = 0.5 * trace;
119 }
120
121 Ok(RemlGpuEvidence {
122 logdet_hessian,
123 gradient_rho,
124 })
125 }
126}
127
128mod cpu_fallback {
129 use super::{RemlGpuEvidence, RemlGpuInput};
130 use ndarray::{Array1, Array2};
131
132 pub(super) fn evidence_derivatives(input: RemlGpuInput<'_>) -> Result<RemlGpuEvidence, String> {
133 let p = input.penalized_hessian.nrows();
134 let mut identity = Array2::<f64>::zeros((p, p));
135 for i in 0..p {
136 identity[[i, i]] = 1.0;
137 }
138 let (_, logdet_hessian) =
139 crate::gpu::pirls_gpu::cholesky_solve_gpu(input.penalized_hessian, identity.view())?;
140 let mut gradient_rho = Array1::<f64>::zeros(input.derivative_hessians.len());
141 for (j, derivative) in input.derivative_hessians.iter().enumerate() {
142 let (solved, _) = crate::gpu::pirls_gpu::cholesky_solve_gpu(
143 input.penalized_hessian,
144 derivative.view(),
145 )?;
146 let mut trace = 0.0_f64;
147 for i in 0..p {
148 trace += solved[[i, i]];
149 }
150 gradient_rho[j] = 0.5 * trace;
151 }
152 Ok(RemlGpuEvidence {
153 logdet_hessian,
154 gradient_rho,
155 })
156 }
157}