Skip to main content

lumen_core/ops/
matmul.rs

1use std::{any::TypeId, ops::Deref};
2use gemm::Parallelism;
3use num_traits::Zero;
4use crate::{utils, AutogradMetaT, Error, Layout, NumDType, Result, Shape, Storage, StorageIndices};
5use crate::Tensor;
6
7impl<T: NumDType> Tensor<T> {
8    /// Returns the matrix-multiplication of the input tensor with the other provided tensor.
9    ///
10    /// # Arguments
11    ///
12    /// * `self` - A tensor with dimensions `b1, b2, ..., bi, m, k`.
13    /// * `rhs` - A tensor with dimensions `b1, b2, ..., bi, k, n`.
14    ///
15    /// The resulting tensor has dimensions `b1, b2, ..., bi, m, n`.
16    pub fn matmul(&self, rhs: &Self) -> Result<Self> {
17        let a_dims = self.shape().dims();
18        let b_dims = rhs.shape().dims();
19
20        let dim = a_dims.len();
21
22        if dim < 2 || b_dims.len() != dim {
23            Err(Error::ShapeMismatchBinaryOp {
24                lhs: self.shape().clone(),
25                rhs: rhs.shape().clone(),
26                op: "matmul",
27            })?
28        }
29
30        let m = a_dims[dim - 2];
31        let k = a_dims[dim - 1];
32        let k2 = b_dims[dim - 2];
33        let n = b_dims[dim - 1];
34
35        let c_shape = Shape::from(&a_dims[..dim - 2]).extend(&[m, n]);
36        if c_shape.element_count() == 0 || k == 0 {
37            return Self::zeros(c_shape);
38        }
39        let batching: usize = a_dims[..dim - 2].iter().product();
40        let batching_b: usize = b_dims[..dim - 2].iter().product();
41        if k != k2 || batching != batching_b {
42            Err(Error::ShapeMismatchBinaryOp {
43                lhs: self.shape().clone(),
44                rhs: rhs.shape().clone(),
45                op: "matmul",
46            })?
47        }
48
49        // (..., m, k) @ (..., k, n)
50        let c_storage = Self::do_matmul(
51            self.storage_read()?.deref(),
52            self.layout(),
53            rhs.storage_read()?.deref(),
54            rhs.layout(),
55            (batching, m, n, k),
56        );
57
58        let meta = T::AutogradMeta::on_matmul_op(self, rhs);
59        Ok(Self::from_storage(c_storage, c_shape, meta))
60    }
61
62    fn do_matmul(
63        lhs: &Storage<T>, lhs_layout: &Layout, 
64        rhs: &Storage<T>, rhs_layout: &Layout, 
65        bmnk: (usize, usize, usize, usize)
66    ) -> Storage<T> 
67        where T: num_traits::Num + Copy + Zero
68    {    
69        let lhs_rank = lhs_layout.shape().rank();
70        let rhs_rank = rhs_layout.shape().rank();
71        let (bs, ms, ns, ks) = bmnk;
72        let mns = ms * ns;
73        let mut dst = vec![T::zero(); bs * mns];
74
75        let l_stride_m = lhs_layout.stride()[lhs_rank - 2] as isize;
76        let l_stride_k = lhs_layout.stride()[lhs_rank - 1] as isize;
77        let r_stride_k = rhs_layout.stride()[rhs_rank - 2] as isize;
78        let r_stride_n = rhs_layout.stride()[rhs_rank - 1] as isize;
79
80        let id = TypeId::of::<T>();
81        if id == TypeId::of::<f32>() || id == TypeId::of::<f64>() {
82            for b in 0..bs {
83                let start_index = b*mns;
84                let dst_slice = &mut dst[start_index..start_index+mns];
85                let l_batch_offset = Self::compute_batch_offset(b, lhs_layout);
86                let r_batch_offset = Self::compute_batch_offset(b, rhs_layout);
87    
88                let parallelism = Parallelism::Rayon(utils::get_num_threads());
89    
90                unsafe {
91                    let lhs_ptr = lhs.data().as_ptr().add(l_batch_offset);
92                    let rhs_ptr = rhs.data().as_ptr().add(r_batch_offset);
93                    let dst_ptr = dst_slice.as_mut_ptr();
94    
95                    gemm::gemm(
96                        /* m: usize = */ ms,
97                        /* n: usize = */ ns,
98                        /* k: usize = */ ks,
99                        /* dst: *mut T = */ dst_ptr,
100                        /* dst_cs: isize = */ 1,
101                        /* dst_rs: isize = */ ns as isize,
102                        /* read_dst: bool = */ false,
103                        /* lhs: *const T = */ lhs_ptr,
104                        /* lhs_cs: isize = */ l_stride_k as isize,
105                        /* lhs_rs: isize = */ l_stride_m as isize,
106                        /* rhs: *const T = */ rhs_ptr,
107                        /* rhs_cs: isize = */ r_stride_n as isize,
108                        /* rhs_rs: isize = */ r_stride_k as isize,
109                        /* alpha: T = */ T::ZERO,
110                        /* beta: T = */ T::ONE,
111                        /* conj_dst: bool = */ false,
112                        /* conj_lhs: bool = */ false,
113                        /* conj_rhs: bool = */ false,
114                        parallelism,
115                    )
116                }
117            }
118        } else {
119            let lhs_data = lhs.data();
120            let rhs_data = rhs.data();
121
122            // dst.par_chunks_mut(ms * ns).enumerate().for_each(|(b, dst_slice)| {
123            (0..bs).for_each(|b| {
124                let l_batch_offset = Self::compute_batch_offset(b, lhs_layout);
125                let r_batch_offset = Self::compute_batch_offset(b, rhs_layout);
126                let dst_slice = &mut dst[b*mns..b*mns+mns];
127
128                for m in 0..ms {
129                    for k in 0..ks {
130                        let l_idx = l_batch_offset + (m as isize * l_stride_m + k as isize * l_stride_k) as usize;
131                        let l_val = unsafe { *lhs_data.get_unchecked(l_idx) };
132                        
133                        let r_base = r_batch_offset + (k as isize * r_stride_k) as usize;
134                        let dst_base = m * ns;
135
136                        for n in 0..ns {
137                            unsafe {
138                                let r_idx = r_base + (n as isize * r_stride_n) as usize;
139                                let r_val = *rhs_data.get_unchecked(r_idx);
140                                
141                                let dst_ptr = dst_slice.get_unchecked_mut(dst_base + n);
142                                *dst_ptr = *dst_ptr + l_val * r_val;
143                            }
144                        }
145                    }
146                }
147            });
148        }
149        
150        Storage::new(dst)
151    }
152
153    fn compute_batch_offset(b: usize, layout: &Layout) -> usize {
154        let rank = layout.shape().rank();
155        let batch_dims = &layout.dims()[..rank - 2];
156        let batch_strides = &layout.stride()[..rank - 2];
157        
158        let mut offset = layout.start_offset();
159        let mut temp_b = b;
160        
161        for i in (0..batch_dims.len()).rev() {
162            let idx = temp_b % batch_dims[i];
163            offset += idx * batch_strides[i];
164            temp_b /= batch_dims[i];
165        }
166        offset
167    }
168}
169
170impl<T: NumDType> Tensor<T> {
171    /// In-place version of matrix multiplication: self += lhs @ rhs
172    pub fn add_matmul_(&self, lhs: &Self, rhs: &Self) -> Result<()> {
173        self.check_implace_op()?;
174
175        let a_dims = lhs.shape().dims();
176        let b_dims = rhs.shape().dims();
177        let c_dims = self.shape().dims();
178
179        let dim = a_dims.len();
180
181        // 1. 基础维度检查
182        if dim < 2 || b_dims.len() != dim || c_dims.len() != dim {
183            return Err(Error::ShapeMismatchBinaryOp {
184                lhs: lhs.shape().clone(),
185                rhs: rhs.shape().clone(),
186                op: "add_matmul_ (rank mismatch)",
187            });
188        }
189
190        let m = a_dims[dim - 2];
191        let k = a_dims[dim - 1];
192        let k2 = b_dims[dim - 2];
193        let n = b_dims[dim - 1];
194        
195        let self_m = c_dims[dim - 2];
196        let self_n = c_dims[dim - 1];
197
198        // 2. 矩阵乘法规则检查 (M,K) @ (K,N) -> (M,N)
199        if k != k2 || m != self_m || n != self_n {
200            return Err(Error::ShapeMismatchBinaryOp {
201                lhs: lhs.shape().clone(),
202                rhs: rhs.shape().clone(),
203                op: "add_matmul_ (dimension mismatch)",
204            });
205        }
206
207        // 3. Batch 检查
208        let batching_lhs: usize = a_dims[..dim - 2].iter().product();
209        let batching_rhs: usize = b_dims[..dim - 2].iter().product();
210        let batching_self: usize = c_dims[..dim - 2].iter().product();
211
212        if batching_lhs != batching_rhs || batching_lhs != batching_self {
213            return Err(Error::ShapeMismatchBinaryOp {
214                lhs: lhs.shape().clone(),
215                rhs: rhs.shape().clone(),
216                op: "add_matmul_ (batch mismatch)",
217            });
218        }
219
220        if m == 0 || n == 0 || k == 0 {
221            return Ok(());
222        }
223
224        // 4. 执行计算
225        self.do_add_matmul_(
226            &mut *self.storage_write()?, self.layout(),
227            lhs.storage_read()?.deref(), lhs.layout(),
228            rhs.storage_read()?.deref(), rhs.layout(),
229            (batching_lhs, m, n, k),
230        );
231        
232        Ok(())
233    }
234
235    fn do_add_matmul_(
236        &self,
237        dst_storage: &mut Storage<T>, dst_layout: &Layout,
238        lhs: &Storage<T>, lhs_layout: &Layout,
239        rhs: &Storage<T>, rhs_layout: &Layout,
240        bmnk: (usize, usize, usize, usize)
241    ) {
242        let (bs, ms, ns, ks) = bmnk;
243        
244        let lhs_rank = lhs_layout.shape().rank();
245        let rhs_rank = rhs_layout.shape().rank();
246
247        let l_stride_m = lhs_layout.stride()[lhs_rank - 2] as isize;
248        let l_stride_k = lhs_layout.stride()[lhs_rank - 1] as isize;
249        let r_stride_k = rhs_layout.stride()[rhs_rank - 2] as isize;
250        let r_stride_n = rhs_layout.stride()[rhs_rank - 1] as isize;
251
252        let id = TypeId::of::<T>();
253        if id == TypeId::of::<f32>() || id == TypeId::of::<f64>() {
254            for b in 0..bs {
255                let l_offset = Self::compute_batch_offset(b, lhs_layout);
256                let r_offset = Self::compute_batch_offset(b, rhs_layout);
257                let d_offset = Self::compute_batch_offset(b, dst_layout);
258
259                unsafe {
260                    let lhs_ptr = lhs.data().as_ptr().add(l_offset);
261                    let rhs_ptr = rhs.data().as_ptr().add(r_offset);
262                    let dst_ptr = dst_storage.data_mut().as_mut_ptr().add(d_offset);
263                    let parallelism = Parallelism::Rayon(utils::get_num_threads());
264
265                    gemm::gemm(
266                        /* m: usize = */ ms,
267                        /* n: usize = */ ns,
268                        /* k: usize = */ ks,
269                        /* dst: *mut T = */ dst_ptr,
270                        /* dst_cs: isize = */ 1,
271                        /* dst_rs: isize = */ ns as isize,
272                        /* read_dst: bool = */ false,
273                        /* lhs: *const T = */ lhs_ptr,
274                        /* lhs_cs: isize = */ l_stride_k as isize,
275                        /* lhs_rs: isize = */ l_stride_m as isize,
276                        /* rhs: *const T = */ rhs_ptr,
277                        /* rhs_cs: isize = */ r_stride_n as isize,
278                        /* rhs_rs: isize = */ r_stride_k as isize,
279                        /* alpha: T = */ T::ONE,
280                        /* beta: T = */ T::ONE,
281                        /* conj_dst: bool = */ false,
282                        /* conj_lhs: bool = */ false,
283                        /* conj_rhs: bool = */ false,
284                        parallelism,
285                    );
286                }
287            }
288        } else {
289            
290            let lhs_data = lhs.data();
291            let rhs_data = rhs.data();
292            
293            match dst_layout.storage_indices() {
294                StorageIndices::Contiguous(index) => {
295                    let dst = &mut dst_storage.data_mut()[index.begin_index..index.end_index];
296                    let mns = ms * ns;
297
298                    (0..bs).for_each(|b| {
299                        let l_batch_offset = Self::compute_batch_offset(b, lhs_layout);
300                        let r_batch_offset = Self::compute_batch_offset(b, rhs_layout);
301                        let dst_slice = &mut dst[b*mns..b*mns+mns];
302        
303                        for m in 0..ms {
304                            for k in 0..ks {
305                                let l_idx = l_batch_offset + (m as isize * l_stride_m + k as isize * l_stride_k) as usize;
306                                let l_val = unsafe { *lhs_data.get_unchecked(l_idx) };
307                                
308                                let r_base = r_batch_offset + (k as isize * r_stride_k) as usize;
309                                let dst_base = m * ns;
310        
311                                for n in 0..ns {
312                                    unsafe {
313                                        let r_idx = r_base + (n as isize * r_stride_n) as usize;
314                                        let r_val = *rhs_data.get_unchecked(r_idx);
315                                        
316                                        let dst_ptr = dst_slice.get_unchecked_mut(dst_base + n);
317                                        *dst_ptr = *dst_ptr + l_val * r_val;
318                                    }
319                                }
320                            }
321                        }
322                    });
323                },
324                StorageIndices::Uncontiguous(_) => {
325                    let dst_rank = dst_layout.shape().rank();
326                    let d_stride_m = dst_layout.stride()[dst_rank - 2] as isize;
327                    let d_stride_n = dst_layout.stride()[dst_rank - 1] as isize;
328
329                    (0..bs).for_each(|b| {
330                        let l_batch_offset = Self::compute_batch_offset(b, lhs_layout);
331                        let r_batch_offset = Self::compute_batch_offset(b, rhs_layout);
332                        let d_batch_offset = Self::compute_batch_offset(b, dst_layout);
333        
334                        for m in 0..ms {
335                            let d_m_base = d_batch_offset + (m as isize * d_stride_m) as usize;
336                            for k in 0..ks {
337                                let l_idx = l_batch_offset + (m as isize * l_stride_m + k as isize * l_stride_k) as usize;
338                                let l_val = unsafe { *lhs_data.get_unchecked(l_idx) };
339                                
340                                let r_k_base = r_batch_offset + (k as isize * r_stride_k) as usize;
341        
342                                for n in 0..ns {
343                                    unsafe {
344                                        let r_idx = r_k_base + (n as isize * r_stride_n) as usize;
345                                        let r_val = *rhs_data.get_unchecked(r_idx);
346                                        
347                                        let d_idx = d_m_base + (n as isize * d_stride_n) as usize;
348                                        let dst_ptr = dst_storage.data_mut();
349                                        dst_ptr[d_idx] += l_val * r_val;
350                                    }
351                                }
352                            }
353                        }
354                    });
355                }
356            }
357        }
358    }
359}
360
361#[cfg(test)]
362#[allow(unused)]
363mod tests {
364    use crate::{s, DType, IndexOp, Slice};
365
366    use super::*;
367
368    #[test]
369    fn test_matmul_2d() {
370        // A: (2, 3), B: (3, 2)
371        let a = Tensor::arange(0, 6).unwrap().reshape((2, 3)).unwrap(); // [[0,1,2],[3,4,5]]
372        let b = Tensor::arange(0, 6).unwrap().reshape((3, 2)).unwrap(); // [[0,1],[2,3],[4,5]]
373        let c = a.matmul(&b).unwrap();
374
375        let expected = Tensor::new(&[
376            [0*0 + 1*2 + 2*4, 0*1 + 1*3 + 2*5], // [10, 13]
377            [3*0 + 4*2 + 5*4, 3*1 + 4*3 + 5*5], // [28, 40]
378        ]).unwrap();
379
380        assert!(c.allclose(&expected, 1e-5, 1e-8).unwrap());
381    }
382
383    #[test]
384    fn test_add_matmul_2d() {
385        // A: (2, 3), B: (3, 2)
386        let a = Tensor::arange(0.0, 6.0).unwrap().reshape((2, 3)).unwrap(); // [[0,1,2],[3,4,5]]
387        let b = Tensor::arange(0.0, 6.0).unwrap().reshape((3, 2)).unwrap(); // [[0,1],[2,3],[4,5]]
388        // let c = a.matmul(&b).unwrap();
389        let c = Tensor::zeros((2, 2)).unwrap(); // (2, 2)
390        c.add_matmul_(&a, &b).unwrap();
391
392        let expected = Tensor::new(&[
393            [0*0 + 1*2 + 2*4, 0*1 + 1*3 + 2*5], // [10, 13]
394            [3*0 + 4*2 + 5*4, 3*1 + 4*3 + 5*5], // [28, 40]
395        ]).unwrap().cast::<f64>().unwrap();
396
397        assert!(c.allclose(&expected, 1e-5, 1e-8).unwrap());
398    }
399
400    #[test]
401    fn test_large_matmul() {
402        let w = Tensor::randn(0.0, 1.0, (128, 1024, 1024)).unwrap();
403        let x = Tensor::randn(0.0, 1.0, (128, 512, 1024)).unwrap();
404
405        let start = std::time::Instant::now();
406        let y = x.matmul(&w).unwrap();
407        let end = std::time::Instant::now();
408        
409        eprintln!("{:?}", end - start);
410    }
411
412    #[test]
413    fn test_small_matmul() {
414        let w = Tensor::randn(0.0, 1.0, (32, 32)).unwrap();
415        let x = Tensor::randn(0.0, 1.0, (32, 32)).unwrap();
416
417        let start = std::time::Instant::now();
418        let y = x.matmul(&w).unwrap();
419        let end = std::time::Instant::now();
420        
421        eprintln!("{:?}", end - start);
422    }
423
424    #[test]
425    fn test_matmul_batch() {
426        // A: (2, 2, 3), B: (2, 3, 2)
427        let a = Tensor::arange(0., 12.).unwrap().reshape((2, 2, 3)).unwrap();
428        let b = Tensor::arange(0., 12.).unwrap().reshape((2, 3, 2)).unwrap();
429        let c = a.matmul(&b).unwrap();
430
431        assert_eq!(c.dims(), &[2, 2, 2]);
432
433        // batch 0
434        let a0 = Tensor::new(&[[0.,1.,2.],[3.,4.,5.]]).unwrap();
435        let b0 = Tensor::new(&[[0.,1.],[2.,3.],[4.,5.]]).unwrap();
436        let c0 = a0.matmul(&b0).unwrap();
437
438        // batch 1
439        let a1 = Tensor::new(&[[6.,7.,8.],[9.,10.,11.]]).unwrap();
440        let b1 = Tensor::new(&[[6.,7.],[8.,9.],[10.,11.]]).unwrap();
441        let c1 = a1.matmul(&b1).unwrap();
442
443        assert!(c0.allclose(&c.index(0).unwrap(), 1e-5, 1e-8).unwrap());
444        assert!(c1.allclose(&c.index(1).unwrap(), 1e-5, 1e-8).unwrap());
445    }
446
447    #[test]
448    fn test_matmul_not_continues() {
449        let a = Tensor::arange(0., 125.).unwrap().reshape((5, 5, 5)).unwrap();
450
451        let sub_a = a.index((s!(1:3), s!(3:5), 2)).unwrap();
452        let mut vals = Vec::new();
453        for i in 1..3 {
454            for j in 3..5 {
455                vals.push((i * 25 + j * 5 + 2) as f64);
456            }
457        }
458        let expected = Tensor::from_vec(vals, (2, 2)).unwrap();
459        assert!(sub_a.allclose(&expected, 0.0, 0.0).unwrap());
460
461        let b = Tensor::randn(0.0, 1.0, (2, 5)).unwrap();
462
463        let res = sub_a.matmul(&b).unwrap();
464        let res_expected = expected.matmul(&b).unwrap();
465        assert!(res.allclose(&res_expected, 1e-5, 1e-8).unwrap());
466    }
467}