1use crate::dtype::DType;
4use crate::error::{Error, Result};
5use crate::ops::{Kernel, MatmulOps};
6use crate::runtime::cpu::{
7 CpuClient, CpuRuntime,
8 helpers::{dispatch_dtype, ensure_contiguous},
9};
10use crate::tensor::Tensor;
11
12impl MatmulOps<CpuRuntime> for CpuClient {
14 fn matmul(&self, a: &Tensor<CpuRuntime>, b: &Tensor<CpuRuntime>) -> Result<Tensor<CpuRuntime>> {
15 use crate::ops::matmul_output_shape;
16
17 if a.dtype() != b.dtype() {
19 return Err(Error::DTypeMismatch {
20 lhs: a.dtype(),
21 rhs: b.dtype(),
22 });
23 }
24
25 let dtype = a.dtype();
26
27 let out_shape = matmul_output_shape(a.shape(), b.shape()).ok_or(Error::ShapeMismatch {
29 expected: a.shape().to_vec(),
30 got: b.shape().to_vec(),
31 })?;
32
33 let a_shape = a.shape();
35 let b_shape = b.shape();
36 let m = if a_shape.len() >= 2 {
37 a_shape[a_shape.len() - 2]
38 } else {
39 1
40 };
41 let k = a_shape[a_shape.len() - 1];
42 let n = b_shape[b_shape.len() - 1];
43
44 let batch_size: usize = out_shape
46 .iter()
47 .take(out_shape.len().saturating_sub(2))
48 .product();
49 let batch_size = batch_size.max(1);
50
51 let a_batch: usize = a_shape
52 .iter()
53 .take(a_shape.len().saturating_sub(2))
54 .product::<usize>()
55 .max(1);
56 let b_batch: usize = b_shape
57 .iter()
58 .take(b_shape.len().saturating_sub(2))
59 .product::<usize>()
60 .max(1);
61
62 if m <= 16 && b_shape.len() >= 2 && dtype != DType::I8 {
67 let b_strides = b.strides();
68 let ndim = b_shape.len();
69 let stride_row = b_strides[ndim - 2]; let stride_col = b_strides[ndim - 1]; if stride_row == 1 && stride_col == k as isize {
75 let a_contig = ensure_contiguous(a)?;
76 let a_ptr = a_contig.ptr();
77 let b_ptr = b.ptr(); let out = Tensor::<CpuRuntime>::empty(&out_shape, dtype, &self.device);
81 let out_ptr = out.ptr();
82 let ldc = n;
83
84 dispatch_dtype!(dtype, T => {
85 for batch in 0..batch_size {
86 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
87 let b_offset = if b_batch > 1 { batch * n * k } else { 0 };
88 let out_offset = batch * m * n;
89
90 #[cfg(feature = "rayon")]
91 {
92 use rayon::prelude::*;
93
94 let min_cols_per_thread = 64usize;
97 let num_threads = rayon::current_num_threads();
98 let chunk_size = ((n + num_threads - 1) / num_threads).max(min_cols_per_thread);
99
100 if n > min_cols_per_thread && num_threads > 1 {
101 let a_send = (a_ptr as usize) + a_offset * std::mem::size_of::<T>();
104 let b_send = (b_ptr as usize) + b_offset * std::mem::size_of::<T>();
105 let out_send = (out_ptr as usize) + out_offset * std::mem::size_of::<T>();
106 let elem_size = std::mem::size_of::<T>();
107
108 self.install_parallelism(|| {
109 (0..n).into_par_iter().step_by(chunk_size).for_each(|col_start| {
110 let col_end = (col_start + chunk_size).min(n);
111 let chunk_n = col_end - col_start;
112 unsafe {
113 let a_base = a_send as *const T;
114 let b_chunk = (b_send + col_start * k * elem_size) as *const T;
115 let out_chunk = (out_send + col_start * elem_size) as *mut T;
116
117 crate::runtime::cpu::kernels::gemv_bt_kernel::<T>(
118 a_base,
119 b_chunk,
120 out_chunk,
121 m, chunk_n, k, n,
122 );
123 }
124 });
125 });
126 } else {
127 unsafe {
128 crate::runtime::cpu::kernels::gemv_bt_kernel::<T>(
129 (a_ptr as *const T).add(a_offset),
130 (b_ptr as *const T).add(b_offset),
131 (out_ptr as *mut T).add(out_offset),
132 m, n, k, ldc,
133 );
134 }
135 }
136 }
137
138 #[cfg(not(feature = "rayon"))]
139 unsafe {
140 crate::runtime::cpu::kernels::gemv_bt_kernel::<T>(
141 (a_ptr as *const T).add(a_offset),
142 (b_ptr as *const T).add(b_offset),
143 (out_ptr as *mut T).add(out_offset),
144 m, n, k, ldc,
145 );
146 }
147 }
148 }, "matmul_gemv_bt");
149
150 return Ok(out);
151 }
152 }
153
154 let a_contig = ensure_contiguous(a)?;
157 let b_contig = ensure_contiguous(b)?;
158
159 let a_ptr = a_contig.ptr();
160 let b_ptr = b_contig.ptr();
161
162 let lda = k;
164 let ldb = n;
165 let ldc = n;
166
167 if dtype == DType::I8 {
169 use crate::runtime::cpu::kernels::matmul_i8_to_i32_kernel;
170
171 let out = Tensor::<CpuRuntime>::empty(&out_shape, DType::I32, &self.device);
172 let out_ptr = out.ptr();
173
174 #[cfg(feature = "rayon")]
175 {
176 use rayon::prelude::*;
177
178 if batch_size > 1 {
179 let min_len = self.rayon_min_len();
180 self.install_parallelism(|| {
181 (0..batch_size)
182 .into_par_iter()
183 .with_min_len(min_len)
184 .for_each(|batch| unsafe {
185 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
186 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
187 let out_offset = batch * m * n;
188
189 matmul_i8_to_i32_kernel(
190 (a_ptr as *const i8).add(a_offset),
191 (b_ptr as *const i8).add(b_offset),
192 (out_ptr as *mut i32).add(out_offset),
193 m,
194 n,
195 k,
196 lda,
197 ldb,
198 ldc,
199 );
200 });
201 });
202 } else {
203 unsafe {
204 matmul_i8_to_i32_kernel(
205 a_ptr as *const i8,
206 b_ptr as *const i8,
207 out_ptr as *mut i32,
208 m,
209 n,
210 k,
211 lda,
212 ldb,
213 ldc,
214 );
215 }
216 }
217 }
218
219 #[cfg(not(feature = "rayon"))]
220 unsafe {
221 for batch in 0..batch_size {
222 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
223 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
224 let out_offset = batch * m * n;
225
226 matmul_i8_to_i32_kernel(
227 (a_ptr as *const i8).add(a_offset),
228 (b_ptr as *const i8).add(b_offset),
229 (out_ptr as *mut i32).add(out_offset),
230 m,
231 n,
232 k,
233 lda,
234 ldb,
235 ldc,
236 );
237 }
238 }
239
240 return Ok(out);
241 }
242
243 let out = Tensor::<CpuRuntime>::empty(&out_shape, dtype, &self.device);
245 let out_ptr = out.ptr();
246
247 dispatch_dtype!(dtype, T => {
249 #[cfg(feature = "rayon")]
250 {
251 use rayon::prelude::*;
252
253 if batch_size > 1 {
254 let min_len = self.rayon_min_len();
255 self.install_parallelism(|| {
256 (0..batch_size)
257 .into_par_iter()
258 .with_min_len(min_len)
259 .for_each(|batch| unsafe {
260 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
261 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
262 let out_offset = batch * m * n;
263
264 <Self as Kernel<CpuRuntime>>::matmul::<T>(
265 self,
266 (a_ptr as *const T).add(a_offset),
267 (b_ptr as *const T).add(b_offset),
268 (out_ptr as *mut T).add(out_offset),
269 m,
270 n,
271 k,
272 lda,
273 ldb,
274 ldc,
275 );
276 });
277 });
278 } else {
279 unsafe {
280 let a_offset = 0;
281 let b_offset = 0;
282 let out_offset = 0;
283 <Self as Kernel<CpuRuntime>>::matmul::<T>(
284 self,
285 (a_ptr as *const T).add(a_offset),
286 (b_ptr as *const T).add(b_offset),
287 (out_ptr as *mut T).add(out_offset),
288 m,
289 n,
290 k,
291 lda,
292 ldb,
293 ldc,
294 );
295 }
296 }
297 }
298
299 #[cfg(not(feature = "rayon"))]
300 unsafe {
301 for batch in 0..batch_size {
302 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
303 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
304 let out_offset = batch * m * n;
305
306 <Self as Kernel<CpuRuntime>>::matmul::<T>(
307 self,
308 (a_ptr as *const T).add(a_offset),
309 (b_ptr as *const T).add(b_offset),
310 (out_ptr as *mut T).add(out_offset),
311 m,
312 n,
313 k,
314 lda,
315 ldb,
316 ldc,
317 );
318 }
319 }
320 }, "matmul");
321
322 Ok(out)
323 }
324
325 fn matmul_bias(
326 &self,
327 a: &Tensor<CpuRuntime>,
328 b: &Tensor<CpuRuntime>,
329 bias: &Tensor<CpuRuntime>,
330 ) -> Result<Tensor<CpuRuntime>> {
331 use crate::ops::{matmul_bias_output_shape, validate_matmul_bias_dtypes};
332 use crate::runtime::cpu::kernels::matmul_bias_kernel;
333
334 let dtype = validate_matmul_bias_dtypes(a.dtype(), b.dtype(), bias.dtype())?;
336
337 let out_shape = matmul_bias_output_shape(a.shape(), b.shape(), bias.shape()).ok_or(
339 Error::ShapeMismatch {
340 expected: a.shape().to_vec(),
341 got: b.shape().to_vec(),
342 },
343 )?;
344
345 let a_shape = a.shape();
347 let b_shape = b.shape();
348 let m = if a_shape.len() >= 2 {
349 a_shape[a_shape.len() - 2]
350 } else {
351 1
352 };
353 let k = a_shape[a_shape.len() - 1];
354 let n = b_shape[b_shape.len() - 1];
355
356 let a_contig = ensure_contiguous(a)?;
358 let b_contig = ensure_contiguous(b)?;
359 let bias_contig = ensure_contiguous(bias)?;
360
361 let batch_size: usize = out_shape
363 .iter()
364 .take(out_shape.len().saturating_sub(2))
365 .product();
366 let batch_size = batch_size.max(1);
367
368 let a_batch: usize = a_shape
369 .iter()
370 .take(a_shape.len().saturating_sub(2))
371 .product::<usize>()
372 .max(1);
373 let b_batch: usize = b_shape
374 .iter()
375 .take(b_shape.len().saturating_sub(2))
376 .product::<usize>()
377 .max(1);
378
379 let out = Tensor::<CpuRuntime>::empty(&out_shape, dtype, &self.device);
381
382 let a_ptr = a_contig.ptr();
383 let b_ptr = b_contig.ptr();
384 let bias_ptr = bias_contig.ptr();
385 let out_ptr = out.ptr();
386
387 let lda = k;
389 let ldb = n;
390 let ldc = n;
391
392 dispatch_dtype!(dtype, T => {
394 #[cfg(feature = "rayon")]
395 {
396 use rayon::prelude::*;
397
398 if batch_size > 1 {
399 let min_len = self.rayon_min_len();
400 self.install_parallelism(|| {
401 (0..batch_size)
402 .into_par_iter()
403 .with_min_len(min_len)
404 .for_each(|batch| unsafe {
405 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
406 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
407 let out_offset = batch * m * n;
408
409 matmul_bias_kernel::<T>(
410 (a_ptr as *const T).add(a_offset),
411 (b_ptr as *const T).add(b_offset),
412 bias_ptr as *const T, (out_ptr as *mut T).add(out_offset),
414 m,
415 n,
416 k,
417 lda,
418 ldb,
419 ldc,
420 );
421 });
422 });
423 } else {
424 unsafe {
425 let a_offset = 0;
426 let b_offset = 0;
427 let out_offset = 0;
428
429 matmul_bias_kernel::<T>(
430 (a_ptr as *const T).add(a_offset),
431 (b_ptr as *const T).add(b_offset),
432 bias_ptr as *const T,
433 (out_ptr as *mut T).add(out_offset),
434 m,
435 n,
436 k,
437 lda,
438 ldb,
439 ldc,
440 );
441 }
442 }
443 }
444
445 #[cfg(not(feature = "rayon"))]
446 unsafe {
447 for batch in 0..batch_size {
448 let a_offset = if a_batch > 1 { batch * m * k } else { 0 };
449 let b_offset = if b_batch > 1 { batch * k * n } else { 0 };
450 let out_offset = batch * m * n;
451
452 matmul_bias_kernel::<T>(
453 (a_ptr as *const T).add(a_offset),
454 (b_ptr as *const T).add(b_offset),
455 bias_ptr as *const T, (out_ptr as *mut T).add(out_offset),
457 m,
458 n,
459 k,
460 lda,
461 ldb,
462 ldc,
463 );
464 }
465 }
466 }, "matmul_bias");
467
468 Ok(out)
469 }
470}