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 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 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 ms,
97 ns,
98 ks,
99 dst_ptr,
100 1,
101 ns as isize,
102 false,
103 lhs_ptr,
104 l_stride_k as isize,
105 l_stride_m as isize,
106 rhs_ptr,
107 r_stride_n as isize,
108 r_stride_k as isize,
109 T::ZERO,
110 T::ONE,
111 false,
112 false,
113 false,
114 parallelism,
115 )
116 }
117 }
118 } else {
119 let lhs_data = lhs.data();
120 let rhs_data = rhs.data();
121
122 (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 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 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 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 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 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 ms,
267 ns,
268 ks,
269 dst_ptr,
270 1,
271 ns as isize,
272 false,
273 lhs_ptr,
274 l_stride_k as isize,
275 l_stride_m as isize,
276 rhs_ptr,
277 r_stride_n as isize,
278 r_stride_k as isize,
279 T::ONE,
280 T::ONE,
281 false,
282 false,
283 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 let a = Tensor::arange(0, 6).unwrap().reshape((2, 3)).unwrap(); let b = Tensor::arange(0, 6).unwrap().reshape((3, 2)).unwrap(); 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], [3*0 + 4*2 + 5*4, 3*1 + 4*3 + 5*5], ]).unwrap();
379
380 assert!(c.allclose(&expected, 1e-5, 1e-8).unwrap());
381 }
382
383 #[test]
384 fn test_add_matmul_2d() {
385 let a = Tensor::arange(0.0, 6.0).unwrap().reshape((2, 3)).unwrap(); let b = Tensor::arange(0.0, 6.0).unwrap().reshape((3, 2)).unwrap(); let c = Tensor::zeros((2, 2)).unwrap(); 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], [3*0 + 4*2 + 5*4, 3*1 + 4*3 + 5*5], ]).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 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 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 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}