singe_cusparse/sddmm.rs
1use singe_cuda::{data_type::DataTypeLike, types::DevicePtr};
2
3use singe_cusparse_sys as sys;
4
5use crate::{
6 context::Context,
7 error::Result,
8 matrix::{DenseMatrixDescriptor, SparseMatrixDescriptor},
9 scalar::Scalar,
10 try_ffi,
11 types::{Operation, SddmmAlgorithm},
12 utility::to_usize,
13};
14
15pub fn sddmm_buffer_size<Compute: DataTypeLike>(
16 ctx: &Context,
17 op_a: Operation,
18 op_b: Operation,
19 alpha: Scalar<'_, Compute>,
20 matrix_a: &DenseMatrixDescriptor,
21 matrix_b: &DenseMatrixDescriptor,
22 beta: Scalar<'_, Compute>,
23 matrix_c: &mut SparseMatrixDescriptor,
24 algorithm: SddmmAlgorithm,
25) -> Result<usize> {
26 matrix_a.ensure_context(ctx)?;
27 matrix_b.ensure_context(ctx)?;
28 matrix_c.ensure_context(ctx)?;
29 ctx.bind()?;
30 ctx.require_scalar_pointer_mode(alpha, beta)?;
31
32 let mut size = 0;
33 unsafe {
34 try_ffi!(sys::cusparseSDDMM_bufferSize(
35 ctx.as_raw(),
36 op_a.into(),
37 op_b.into(),
38 alpha.ptr().cast(),
39 matrix_a.as_raw_const(),
40 matrix_b.as_raw_const(),
41 beta.ptr().cast(),
42 matrix_c.as_raw(),
43 Compute::data_type().into(),
44 algorithm.into(),
45 &raw mut size,
46 ))?;
47 }
48 to_usize(size, "sddmm buffer size")
49}
50
51pub fn sddmm_preprocess<Compute: DataTypeLike>(
52 ctx: &Context,
53 op_a: Operation,
54 op_b: Operation,
55 alpha: Scalar<'_, Compute>,
56 matrix_a: &DenseMatrixDescriptor,
57 matrix_b: &DenseMatrixDescriptor,
58 beta: Scalar<'_, Compute>,
59 matrix_c: &mut SparseMatrixDescriptor,
60 algorithm: SddmmAlgorithm,
61 external_buffer: Option<DevicePtr>,
62) -> Result<()> {
63 matrix_a.ensure_context(ctx)?;
64 matrix_b.ensure_context(ctx)?;
65 matrix_c.ensure_context(ctx)?;
66 ctx.bind()?;
67 ctx.require_scalar_pointer_mode(alpha, beta)?;
68
69 unsafe {
70 try_ffi!(sys::cusparseSDDMM_preprocess(
71 ctx.as_raw(),
72 op_a.into(),
73 op_b.into(),
74 alpha.ptr().cast(),
75 matrix_a.as_raw_const(),
76 matrix_b.as_raw_const(),
77 beta.ptr().cast(),
78 matrix_c.as_raw(),
79 Compute::data_type().into(),
80 algorithm.into(),
81 external_buffer.unwrap_or(DevicePtr::null()).as_ptr() as _,
82 ))?;
83 }
84 Ok(())
85}
86
87/// Multiplies `matrix_a` and `matrix_b`, then applies the sparsity pattern of `matrix_c`.
88/// Formally, it computes $C = \alpha (op(A) op(B)) \circ spy(C) + \beta C$,
89/// where:
90///
91/// * `op(A)` is a dense matrix of size $m \times k$.
92/// * `op(B)` is a dense matrix of size $k \times n$.
93/// * `C` is a sparse matrix of size $m \times n$.
94/// * `alpha` and `beta` are scalars.
95/// * $\circ$ denotes the Hadamard (entry-wise) matrix product, and `spy(C)` is the structural sparsity pattern of `C`, equal to `1` where `C` stores an entry and `0` elsewhere.
96///
97/// `op(A)` is selected by `op_a` and may be `A`, `A^T`, or `A^H`.
98/// `op(B)` is selected by `op_b` and may be `B`, `B^T`, or `B^H`.
99///
100/// [`sddmm_buffer_size`] returns the workspace size needed by [`sddmm`] or [`sddmm_preprocess`].
101///
102/// Calling [`sddmm_preprocess`] is optional.
103/// It may accelerate subsequent calls to [`sddmm`].
104/// Useful when [`sddmm`] is called multiple times with the same sparsity
105/// pattern (`matrix_c`).
106///
107/// Calling [`sddmm_preprocess`] with `buffer` makes that buffer active for `matrix_c` SDDMM calls.
108/// Subsequent calls to [`sddmm`] with `matrix_c` and the active buffer must use the same values for all parameters as the call to [`sddmm_preprocess`].
109/// The exceptions are: `alpha`, `beta`, `matrix_a`, `matrix_b`, and the values (but not indices) of `matrix_c` may be different.
110/// Importantly, the buffer contents must be unmodified since the call to [`sddmm_preprocess`].
111/// When [`sddmm`] is called with `matrix_c` and its active buffer, it may read acceleration data from the buffer.
112///
113/// Calling [`sddmm_preprocess`] again with `matrix_c` and a new buffer makes the
114/// new buffer active and makes the previously active buffer inactive.
115/// For [`sddmm`], there can only be one active buffer per sparse matrix at a time.
116/// To get the effect of multiple active buffers for a single sparse matrix, create multiple matrix handles that all point to the same index and value buffers, and call [`sddmm_preprocess`] once per handle with different workspace buffers.
117///
118/// Calling [`sddmm`] with an inactive buffer is always permitted.
119/// However, there may be no acceleration from the preprocessing in that case.
120///
121/// For the purposes of thread safety, [`sddmm_preprocess`] is writing to `matrix_c` internal state.
122///
123/// Currently supported sparse matrix formats:
124///
125/// * [`Format::Csr`](crate::types::Format::Csr)
126/// * [`Format::Bsr`](crate::types::Format::Bsr)
127///
128/// [`sddmm`] supports the following index type for representing `matrix_c`:
129///
130/// * 32-bit indices ([`IndexType::I32`](crate::types::IndexType::I32))
131/// * 64-bit indices ([`IndexType::I64`](crate::types::IndexType::I64))
132///
133/// The data type combinations currently supported for [`sddmm`] are listed below:
134///
135/// Uniform-precision computation:
136///
137/// | `A`/`B`/`C`/`compute_type` |
138/// | --- |
139/// | [`DataType::F32`](singe_cuda::data_type::DataType::F32) |
140/// | [`DataType::F64`](singe_cuda::data_type::DataType::F64) |
141/// | [`DataType::ComplexF32`](singe_cuda::data_type::DataType::ComplexF32) |
142/// | [`DataType::ComplexF64`](singe_cuda::data_type::DataType::ComplexF64) |
143///
144/// Mixed-precision computation:
145///
146/// | `A`/`B` | `C` | `compute_type` |
147/// | --- | --- | --- |
148/// | [`DataType::F16`](singe_cuda::data_type::DataType::F16) | [`DataType::F32`](singe_cuda::data_type::DataType::F32) | [`DataType::F32`](singe_cuda::data_type::DataType::F32) |
149/// | [`DataType::F16`](singe_cuda::data_type::DataType::F16) | [`DataType::F16`](singe_cuda::data_type::DataType::F16) | |
150///
151/// [`sddmm`] for [`Format::Bsr`](crate::types::Format::Bsr) also supports the following mixed-precision computation:
152///
153/// | `A`/`B` | `C` | `compute_type` |
154/// | --- | --- | --- |
155/// | [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) | [`DataType::F32`](singe_cuda::data_type::DataType::F32) | [`DataType::F32`](singe_cuda::data_type::DataType::F32) |
156/// | [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) | [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) | |
157///
158/// [`DataType::F16`](singe_cuda::data_type::DataType::F16) and [`DataType::Bf16`](singe_cuda::data_type::DataType::Bf16) always imply mixed-precision computation.
159///
160/// [`sddmm`] for [`Format::Bsr`](crate::types::Format::Bsr) supports block sizes of 2, 4, 8, 16, 32, 64 and 128.
161///
162/// [`sddmm`] supports the following algorithms:
163///
164/// | Algorithm | Notes |
165/// | --- | --- |
166/// | [`SddmmAlgorithm::Default`] | Default algorithm. |
167///
168/// It supports batched computation.
169///
170/// Performance notes: [`sddmm`] for [`Format::Csr`](crate::types::Format::Csr) provides the best performance when `matrix_a` and `matrix_b` satisfy:
171///
172/// * For `matrix_a`:
173///
174/// * `matrix_a` is in row-major order and `op_a` is [`Operation::NonTranspose`], or
175/// * `matrix_a` is in col-major order and `op_a` is not [`Operation::NonTranspose`].
176/// * For `matrix_b`:
177///
178/// * `matrix_b` is in col-major order and `op_b` is [`Operation::NonTranspose`], or
179/// * `matrix_b` is in row-major order and `op_b` is not [`Operation::NonTranspose`].
180///
181/// [`sddmm`] for [`Format::Bsr`](crate::types::Format::Bsr) provides the best performance when `matrix_a` and `matrix_b` satisfy:
182///
183/// * For `matrix_a`:
184///
185/// * `matrix_a` is in row-major order and `op_a` is [`Operation::NonTranspose`], or
186/// * `matrix_a` is in col-major order and `op_a` is not [`Operation::NonTranspose`].
187/// * For `matrix_b`:
188///
189/// * `matrix_b` is in row-major order and `op_b` is [`Operation::NonTranspose`], or
190/// * `matrix_b` is in col-major order and `op_b` is not [`Operation::NonTranspose`].
191///
192/// [`sddmm`] supports the following batch modes:
193///
194/// * $C\_{i} = (A \cdot B) \circ C\_{i}$
195/// * $C\_{i} = \left( A\_{i} \cdot B \right) \circ C\_{i}$
196/// * $C\_{i} = \left( A \cdot B\_{i} \right) \circ C\_{i}$
197/// * $C\_{i} = \left( A\_{i} \cdot B\_{i} \right) \circ C\_{i}$
198///
199/// The number of batches and their strides can be set by using [`SparseMatrixDescriptor::set_csr_strided_batch`] and [`DenseMatrixDescriptor::set_strided_batch`].
200/// The maximum number of batches for [`sddmm`] is 65,535.
201///
202/// [`sddmm`] has the following properties:
203///
204/// * Requires no extra storage.
205/// * Provides deterministic (bitwise) results for each run.
206/// * Supports asynchronous execution.
207/// * Allows the indices of `matrix_c` to be unsorted.
208///
209/// [`sddmm`] supports the following optimizations:
210///
211/// * CUDA graph capture.
212/// * Hardware Memory Compression.
213pub fn sddmm<Compute: DataTypeLike>(
214 ctx: &Context,
215 op_a: Operation,
216 op_b: Operation,
217 alpha: Scalar<'_, Compute>,
218 matrix_a: &DenseMatrixDescriptor,
219 matrix_b: &DenseMatrixDescriptor,
220 beta: Scalar<'_, Compute>,
221 matrix_c: &mut SparseMatrixDescriptor,
222 algorithm: SddmmAlgorithm,
223 external_buffer: Option<DevicePtr>,
224) -> Result<()> {
225 matrix_a.ensure_context(ctx)?;
226 matrix_b.ensure_context(ctx)?;
227 matrix_c.ensure_context(ctx)?;
228 ctx.bind()?;
229 ctx.require_scalar_pointer_mode(alpha, beta)?;
230
231 unsafe {
232 try_ffi!(sys::cusparseSDDMM(
233 ctx.as_raw(),
234 op_a.into(),
235 op_b.into(),
236 alpha.ptr().cast(),
237 matrix_a.as_raw_const(),
238 matrix_b.as_raw_const(),
239 beta.ptr().cast(),
240 matrix_c.as_raw(),
241 Compute::data_type().into(),
242 algorithm.into(),
243 external_buffer.unwrap_or(DevicePtr::null()).as_ptr() as _,
244 ))?;
245 }
246 Ok(())
247}