Skip to main content

burn_cubecl/kernel/matmul/
base.rs

1use super::init_matmul_output;
2use crate::{kernel::quantization::dequantize, tensor::CubeTensor};
3use burn_backend::cubecl::dtype_to_storage_type;
4use burn_backend::{DType, TensorMetadata};
5use burn_std::{MatmulTransformAnalysis, MatmulTransformPolicy};
6use cubek::{
7    matmul::{
8        definition::{MatmulElems, MatmulGlobalElems, MatmulSetupError},
9        strategy::Strategy,
10    },
11    std::InputBinding,
12};
13
14#[cfg(feature = "autotune")]
15use super::matmul_autotune;
16
17/// The strategy to be used when launching a matmul kernel.
18pub enum MatmulStrategy {
19    #[cfg(feature = "autotune")]
20    /// Using autotune to choose the best kernel based on runtime information.
21    Autotune,
22    /// Cube implementation of matmul.
23    Cube,
24}
25
26impl Default for MatmulStrategy {
27    fn default() -> Self {
28        // if autotune is enabled, default to autotune
29        #[cfg(feature = "autotune")]
30        return MatmulStrategy::Autotune;
31
32        #[cfg(not(feature = "autotune"))]
33        MatmulStrategy::Cube
34    }
35}
36
37fn is_two_level(tensor: &CubeTensor) -> bool {
38    match tensor.dtype {
39        DType::QFloat(scheme) => burn_backend::quantization::global_scale_dtype(&scheme).is_some(),
40        _ => false,
41    }
42}
43
44fn maybe_dequantize(tensor: CubeTensor, dtype: DType) -> CubeTensor {
45    if is_two_level(&tensor) {
46        dequantize(tensor, dtype)
47    } else {
48        tensor
49    }
50}
51
52/// Launch a matmul kernel using the given strategy.
53pub fn matmul(
54    lhs: CubeTensor,
55    rhs: CubeTensor,
56    out: Option<CubeTensor>,
57    strategy: MatmulStrategy,
58    out_dtype: DType,
59) -> Result<CubeTensor, MatmulSetupError> {
60    let out = out.unwrap_or_else(|| init_matmul_output(&lhs, &rhs, out_dtype));
61
62    // No quantized matmul kernel applies a per-tensor scale, and the autotune candidates panic on
63    // the level rather than decline it, taking the whole tuning run down with them.
64    let mut lhs = maybe_dequantize(lhs, out_dtype);
65    let rhs = maybe_dequantize(rhs, out_dtype);
66
67    // A broadcast-rhs batched matmul that would tile poorly is folded into a
68    // single matmul: `[.., b, m, k] @ [.., 1, k, n]` runs as `[.., 1, b*m, k]`
69    // instead of `b` matmuls that each re-read the whole rhs. Pure metadata —
70    // the launch operands share the handles, the returned tensor keeps the
71    // broadcast shape.
72    let mut out_launch = out.clone();
73    if lhs.qparams.is_none() {
74        let analysis = MatmulTransformAnalysis::from_metadata(&lhs.meta, &rhs.meta, &out.meta);
75        let action = MatmulTransformPolicy::default().action(&analysis);
76        action.apply(&mut lhs.meta);
77        action.apply(&mut out_launch.meta);
78    }
79
80    match strategy {
81        MatmulStrategy::Cube => {
82            launch_matmul(&Strategy::default(), lhs, rhs, out_launch)?;
83            Ok(out)
84        }
85        #[cfg(feature = "autotune")]
86        MatmulStrategy::Autotune => {
87            matmul_autotune(lhs, rhs, Some(out_launch), out_dtype);
88            Ok(out)
89        }
90    }
91}
92
93pub(crate) fn launch_matmul_naive<S: Clone + Into<Strategy>>(
94    strategy: &S,
95    mut lhs: CubeTensor,
96    mut rhs: CubeTensor,
97    out: CubeTensor,
98) -> Result<(), MatmulSetupError> {
99    // Naive has very specific layout requirements for block scaled tensors, so we need to manually
100    // dequantize if it fails to launch normally. This is because naive is assumed to always work.
101    if lhs.qparams.is_some() || rhs.qparams.is_some() {
102        match launch_matmul(strategy, lhs.clone(), rhs.clone(), out.clone()) {
103            Err(_) => {
104                if lhs.qparams.is_some() {
105                    lhs = dequantize(lhs, out.dtype);
106                }
107                if rhs.qparams.is_some() {
108                    rhs = dequantize(rhs, out.dtype);
109                }
110                launch_matmul(strategy, lhs, rhs, out)
111            }
112            Ok(_) => Ok(()),
113        }
114    } else {
115        launch_matmul(strategy, lhs, rhs, out)
116    }
117}
118
119pub(crate) fn launch_matmul<S: Clone + Into<Strategy>>(
120    strategy: &S,
121    lhs: CubeTensor,
122    mut rhs: CubeTensor,
123    out: CubeTensor,
124) -> Result<(), MatmulSetupError> {
125    let strategy: Strategy = strategy.clone().into();
126    let client = &out.client;
127
128    let lhs_quant_handles = lhs.quantized_handles();
129    let out_dtype: DType = out.dtype;
130
131    let (lhs_dtype, lhs_handle) = match lhs_quant_handles {
132        None => {
133            let lhs_dtype = lhs.dtype;
134            (
135                lhs_dtype,
136                InputBinding::new(lhs.binding(), dtype_to_storage_type(lhs_dtype)),
137            )
138        }
139        Some((data, scale)) => {
140            let scheme = lhs.scheme();
141            let data_dtype = data.dtype;
142            let scale_dtype = scale.dtype;
143            (
144                out_dtype,
145                InputBinding::quantized(
146                    data.binding(),
147                    scale.binding(),
148                    lhs.meta.shape().clone(),
149                    scheme,
150                    dtype_to_storage_type(data_dtype),
151                    dtype_to_storage_type(scale_dtype),
152                ),
153            )
154        }
155    };
156
157    let rhs_quant_handles = rhs.quantized_handles();
158
159    let (rhs_dtype, rhs_handle) = match rhs_quant_handles {
160        None => (
161            lhs_dtype,
162            InputBinding::new(rhs.binding(), dtype_to_storage_type(lhs_dtype)),
163        ),
164        Some((data, scale)) => {
165            // Extremely hacky fix to ensure naive can run in every case
166            if matches!(
167                strategy,
168                Strategy::MultiLevel(cubek::matmul::multi_level::Strategy::Naive)
169            ) && rhs.scheme().block_size().is_some()
170            {
171                rhs = dequantize(rhs.clone(), lhs_dtype);
172                let rhs_dtype = rhs.dtype;
173                (
174                    lhs_dtype,
175                    InputBinding::new(rhs.binding(), dtype_to_storage_type(rhs_dtype)),
176                )
177            } else {
178                let scheme = rhs.scheme();
179                let data_dtype = data.dtype;
180                let scale_dtype = scale.dtype;
181                (
182                    out_dtype,
183                    InputBinding::quantized(
184                        data.binding(),
185                        scale.binding(),
186                        rhs.meta.shape().clone(),
187                        scheme,
188                        dtype_to_storage_type(data_dtype),
189                        dtype_to_storage_type(scale_dtype),
190                    ),
191                )
192            }
193        }
194    };
195
196    let mut dtypes = MatmulElems::from_globals(&MatmulGlobalElems {
197        lhs: dtype_to_storage_type(lhs_dtype),
198        rhs: dtype_to_storage_type(rhs_dtype),
199        out: dtype_to_storage_type(out_dtype),
200    });
201
202    cubek::matmul::launch::launch_ref(
203        &strategy,
204        client,
205        lhs_handle,
206        rhs_handle,
207        out.clone().binding(),
208        &mut dtypes,
209    )?;
210
211    Ok(())
212}