Skip to main content

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}