1use crate::error::TruenoError;
7
8use super::compute::{gemm_blis, gemm_blis_with_prepacked_b};
9use super::prepacked::PrepackedB;
10#[cfg(feature = "parallel")]
11use super::{MC, MR};
12
13#[derive(Debug, Clone)]
15pub struct HeijunkaScheduler {
16 pub num_threads: usize,
18 pub variance_threshold: f32,
20}
21
22impl Default for HeijunkaScheduler {
23 fn default() -> Self {
24 #[cfg(feature = "parallel")]
25 let threads = rayon::current_num_threads();
26 #[cfg(not(feature = "parallel"))]
27 let threads = 1;
28
29 Self {
30 num_threads: threads,
31 variance_threshold: 0.05, }
33 }
34}
35
36impl HeijunkaScheduler {
37 pub fn partition_m(&self, m: usize, mc: usize) -> Vec<std::ops::Range<usize>> {
39 let num_blocks = (m + mc - 1) / mc;
40 let blocks_per_thread = num_blocks / self.num_threads;
41 let remainder = num_blocks % self.num_threads;
42
43 let mut partitions = Vec::with_capacity(self.num_threads);
44 let mut start_block = 0;
45
46 for t in 0..self.num_threads {
47 let extra = if t < remainder { 1 } else { 0 };
48 let thread_blocks = blocks_per_thread + extra;
49
50 let start_row = start_block * mc;
51 let end_row = ((start_block + thread_blocks) * mc).min(m);
52
53 if start_row < end_row {
54 partitions.push(start_row..end_row);
55 }
56
57 start_block += thread_blocks;
58 }
59
60 partitions
61 }
62}
63
64#[cfg(feature = "parallel")]
73pub(crate) fn gemm_should_run_serial(m: usize, n: usize, k: usize) -> bool {
74 let flops = m * n * k;
75 flops < 8_000_000 || (flops < 64_000_000 && n < 192)
76}
77
78#[cfg(feature = "parallel")]
80pub fn gemm_blis_parallel(
81 m: usize,
82 n: usize,
83 k: usize,
84 a: &[f32],
85 b: &[f32],
86 c: &mut [f32],
87) -> Result<(), TruenoError> {
88 use rayon::prelude::*;
89 contract_pre_amdahl_speedup!();
90
91 if a.len() != m * k || b.len() != k * n || c.len() != m * n {
93 return Err(TruenoError::InvalidInput("Dimension mismatch".to_string()));
94 }
95
96 let flops = m * n * k;
100 if gemm_should_run_serial(m, n, k) {
101 return gemm_blis(m, n, k, a, b, c, None);
102 }
103
104 let phys_cores = num_cpus::get_physical();
116 let max_threads = if flops < 64_000_000 {
117 2.min(phys_cores)
119 } else if flops < 512_000_000 {
120 4.min(phys_cores)
122 } else if flops < 4_000_000_000 {
123 8.min(phys_cores)
129 } else {
130 (phys_cores / 2).max(8).min(phys_cores)
133 };
134
135 let mut scheduler = HeijunkaScheduler::default();
136 scheduler.num_threads = scheduler.num_threads.min(max_threads);
137 let ps = if m <= MC { MR.max(m / scheduler.num_threads) } else { MC };
138 let partitions = scheduler.partition_m(m, ps);
139
140 let c_ptr = c.as_mut_ptr() as usize;
147
148 partitions.into_par_iter().for_each(|m_range| {
149 let m_local = m_range.len();
150 let m_start = m_range.start;
151
152 let a_local = &a[m_start * k..(m_start + m_local) * k];
153
154 let c_local = unsafe {
156 let ptr = c_ptr as *mut f32;
157 std::slice::from_raw_parts_mut(ptr.add(m_start * n), m_local * n)
158 };
159
160 let _ = gemm_blis(m_local, n, k, a_local, b, c_local, None);
161 });
162
163 Ok(())
164}
165
166#[cfg(feature = "parallel")]
177pub fn gemm_blis_parallel_shared_b(
178 m: usize,
179 n: usize,
180 k: usize,
181 a: &[f32],
182 b: &[f32],
183 c: &mut [f32],
184) -> Result<(), TruenoError> {
185 use rayon::prelude::*;
186
187 if a.len() != m * k || b.len() != k * n || c.len() != m * n {
188 return Err(TruenoError::InvalidInput("Dimension mismatch".to_string()));
189 }
190
191 let flops = m * n * k;
193 if flops < 8_000_000 {
194 return gemm_blis(m, n, k, a, b, c, None);
195 }
196
197 #[cfg(target_arch = "x86_64")]
199 if !std::arch::is_x86_feature_detected!("avx512f") {
200 return gemm_blis(m, n, k, a, b, c, None);
201 }
202
203 let phys_cores = num_cpus::get_physical();
204 let max_threads = if flops < 64_000_000 {
205 2.min(phys_cores)
206 } else if flops < 512_000_000 {
207 4.min(phys_cores)
208 } else if flops < 4_000_000_000 {
209 (phys_cores / 2).max(8).min(phys_cores)
212 } else {
213 (phys_cores / 2).max(8).min(phys_cores)
214 };
215
216 let blk = super::cache_topology::blocking_8x32();
217 let mr = blk.mr; let nr = blk.nr; let mc = blk.mc.min(m);
220 let nc = blk.nc.min(n);
221 let kc = blk.kc;
222
223 let b_panels = (nc + nr - 1) / nr;
225 let packed_b_size = b_panels * nr * kc;
226 let mut packed_b = vec![0.0f32; packed_b_size];
227
228 let c_ptr = c.as_mut_ptr() as usize;
229 let num_threads = max_threads.min(rayon::current_num_threads());
230
231 for jc in (0..n).step_by(nc) {
232 let nc_block = nc.min(n - jc);
233
234 for pc in (0..k).step_by(kc) {
235 let kc_block = kc.min(k - pc);
236
237 super::compute::pack_b_block_generic(
239 b,
240 n,
241 pc,
242 jc,
243 kc_block,
244 nc_block,
245 nr,
246 &mut packed_b,
247 );
248 let shared_b: &[f32] = &packed_b;
249
250 let m_per_thread = ((m + num_threads - 1) / num_threads + mr - 1) / mr * mr;
252
253 (0..num_threads).into_par_iter().for_each(|tid| {
254 let ic_start = tid * m_per_thread;
255 if ic_start >= m {
256 return;
257 }
258 let ic_end = (ic_start + m_per_thread).min(m);
259
260 thread_local! {
263 static TL_A: std::cell::RefCell<Vec<f32>> =
264 const { std::cell::RefCell::new(Vec::new()) };
265 }
266 TL_A.with(|tl| {
267 let a_panels = (m_per_thread + mr - 1) / mr;
268 let needed = a_panels * mr * kc_block;
269 let mut packed_a = tl.borrow_mut();
270 if packed_a.len() < needed {
271 packed_a.resize(needed, 0.0);
272 }
273
274 let panels_n = (nc_block + nr - 1) / nr;
275
276 for ic in (ic_start..ic_end).step_by(mc) {
277 let mc_block = mc.min(ic_end - ic);
278
279 super::packing::pack_a_block(
280 a,
281 k,
282 ic,
283 pc,
284 mc_block,
285 kc_block,
286 &mut packed_a,
287 );
288
289 let panels_m = (mc_block + mr - 1) / mr;
290
291 for ir_panel in 0..panels_m {
292 let ir = ir_panel * mr;
293 let mr_block = mr.min(mc_block - ir);
294
295 for jr_panel in 0..panels_n {
296 let jr = jr_panel * nr;
297 let nr_block = nr.min(nc_block - jr);
298
299 let a_panel = &packed_a[ir_panel * mr * kc_block..];
300 let b_panel = &shared_b[jr_panel * nr * kc_block..];
301
302 if mr_block == 8 && nr_block == 32 {
303 #[cfg(target_arch = "x86_64")]
304 unsafe {
305 super::compute::avx512_microkernel_8x32_rowmajor(
306 kc_block,
307 a_panel.as_ptr(),
308 b_panel.as_ptr(),
309 (c_ptr as *mut f32).add((ic + ir) * n + (jc + jr)),
310 n,
311 );
312 }
313 } else {
314 for ir_local in 0..mr_block {
316 for jr_local in 0..nr_block {
317 let mut sum = 0.0f32;
318 for p in 0..kc_block {
319 sum += a_panel[p * mr + ir_local]
320 * b_panel[p * nr + jr_local];
321 }
322 unsafe {
323 let c = c_ptr as *mut f32;
324 *c.add(
325 (ic + ir + ir_local) * n + (jc + jr + jr_local),
326 ) += sum;
327 }
328 }
329 }
330 }
331 }
332 }
333 }
334 }); });
336 }
337 }
338
339 Ok(())
340}
341
342#[cfg(not(feature = "parallel"))]
344pub fn gemm_blis_parallel(
345 m: usize,
346 n: usize,
347 k: usize,
348 a: &[f32],
349 b: &[f32],
350 c: &mut [f32],
351) -> Result<(), TruenoError> {
352 gemm_blis(m, n, k, a, b, c, None)
353}
354
355#[cfg(feature = "parallel")]
366pub fn gemm_blis_parallel_with_prepacked_b(
367 m: usize,
368 n: usize,
369 k: usize,
370 a: &[f32],
371 prepacked_b: &PrepackedB,
372 c: &mut [f32],
373) -> Result<(), TruenoError> {
374 use rayon::prelude::*;
375
376 if a.len() != m * k || c.len() != m * n {
377 return Err(TruenoError::InvalidInput("Dimension mismatch".to_string()));
378 }
379 if prepacked_b.k != k || prepacked_b.n != n {
380 return Err(TruenoError::InvalidInput(format!(
381 "PrepackedB dimension mismatch: expected ({}, {}), got ({}, {})",
382 k, n, prepacked_b.k, prepacked_b.n
383 )));
384 }
385
386 if m * n * k < 1_000_000 {
388 return gemm_blis_with_prepacked_b(m, n, k, a, prepacked_b, c, None);
389 }
390
391 let scheduler = HeijunkaScheduler::default();
392 let partitions = scheduler.partition_m(m, MC);
393
394 let c_ptr = c.as_mut_ptr() as usize;
395
396 partitions.into_par_iter().for_each(|m_range| {
398 let m_local = m_range.len();
399 let m_start = m_range.start;
400
401 let a_local = &a[m_start * k..(m_start + m_local) * k];
402
403 let c_local = unsafe {
406 let ptr = c_ptr as *mut f32;
407 std::slice::from_raw_parts_mut(ptr.add(m_start * n), m_local * n)
408 };
409
410 let _ = gemm_blis_with_prepacked_b(m_local, n, k, a_local, prepacked_b, c_local, None);
411 });
412
413 Ok(())
414}
415
416#[cfg(not(feature = "parallel"))]
418pub fn gemm_blis_parallel_with_prepacked_b(
419 m: usize,
420 n: usize,
421 k: usize,
422 a: &[f32],
423 prepacked_b: &PrepackedB,
424 c: &mut [f32],
425) -> Result<(), TruenoError> {
426 gemm_blis_with_prepacked_b(m, n, k, a, prepacked_b, c, None)
427}