Skip to main content

gam_gpu/
memory.rs

1use ndarray::{Array1, Array2};
2
3#[derive(Clone, Debug)]
4pub struct DeviceBuffer<T> {
5    host_shadow: Vec<T>,
6}
7
8impl<T> DeviceBuffer<T> {
9    pub const fn from_host_shadow(host_shadow: Vec<T>) -> Self {
10        Self { host_shadow }
11    }
12
13    pub const fn len(&self) -> usize {
14        self.host_shadow.len()
15    }
16
17    pub const fn is_empty(&self) -> bool {
18        self.host_shadow.len() == 0
19    }
20
21    pub fn host_shadow(&self) -> &[T] {
22        &self.host_shadow
23    }
24}
25
26#[derive(Clone, Debug)]
27pub struct DeviceVector {
28    pub len: usize,
29    pub data: DeviceBuffer<f64>,
30}
31
32impl DeviceVector {
33    pub fn from_array(array: &Array1<f64>) -> Self {
34        Self {
35            len: array.len(),
36            data: DeviceBuffer::from_host_shadow(array.to_vec()),
37        }
38    }
39}
40
41#[derive(Clone, Debug)]
42pub struct DeviceMatrix {
43    pub rows: usize,
44    pub cols: usize,
45    pub data: DeviceBuffer<f64>,
46    pub column_major: bool,
47}
48
49impl DeviceMatrix {
50    pub fn from_array(array: &Array2<f64>) -> Self {
51        Self {
52            rows: array.nrows(),
53            cols: array.ncols(),
54            data: DeviceBuffer::from_host_shadow(array.iter().copied().collect()),
55            column_major: false,
56        }
57    }
58
59    pub const fn bytes(&self) -> usize {
60        self.rows
61            .saturating_mul(self.cols)
62            .saturating_mul(std::mem::size_of::<f64>())
63    }
64}
65
66#[derive(Clone, Debug)]
67pub struct DeviceCsrMatrix {
68    pub rows: usize,
69    pub cols: usize,
70    pub rowptr: DeviceBuffer<i32>,
71    pub colidx: DeviceBuffer<i32>,
72    pub values: DeviceBuffer<f64>,
73}
74
75impl DeviceCsrMatrix {
76    /// Construct a CSR matrix, enforcing the structural invariant that `rowptr`
77    /// holds exactly `rows + 1` entries.
78    ///
79    /// A CSR row-pointer array must have one slot per row plus a trailing slot
80    /// equal to `nnz`. If the supplied `rowptr` violates this (too short or too
81    /// long), it is canonicalized to `rows + 1` monotone entries: a short
82    /// `rowptr` is padded with its final value (marking the remaining rows as
83    /// empty) and an over-long `rowptr` is truncated. This prevents downstream
84    /// row-slice and deallocation paths from indexing `rowptr[row + 1]` out of
85    /// bounds, which would be an invalid-free / out-of-bounds deallocation
86    /// hazard.
87    pub fn new(
88        rows: usize,
89        cols: usize,
90        rowptr: DeviceBuffer<i32>,
91        colidx: DeviceBuffer<i32>,
92        values: DeviceBuffer<f64>,
93    ) -> Self {
94        let expected = rows + 1;
95        let mut ptr = rowptr.host_shadow().to_vec();
96        if ptr.len() != expected {
97            let fill = ptr.last().copied().unwrap_or(0);
98            ptr.resize(expected, fill);
99        }
100        Self {
101            rows,
102            cols,
103            rowptr: DeviceBuffer::from_host_shadow(ptr),
104            colidx,
105            values,
106        }
107    }
108
109    pub const fn nnz(&self) -> usize {
110        self.values.len()
111    }
112}