1use crate::fused::CostEstimate;
40use crate::kernel::{CostFn, OpParams, unknown_cost};
41use fuel_ir::backend::BackendCapabilities;
42use fuel_ir::dispatch::OpKind;
43use fuel_ir::{DType, Shape};
44
45pub(crate) fn dtype_bytes(dt: DType) -> u64 {
57 match dt {
58 DType::F32 | DType::U32 | DType::I32 => 4,
59 DType::F64 | DType::I64 => 8,
60 DType::BF16 | DType::F16 | DType::I16 => 2,
61 DType::U8 | DType::I8 => 1,
62 DType::Bool => 1,
64 DType::F8E4M3 | DType::F8E5M2 | DType::F8E8M0 | DType::F8E6M2 => 1,
65 DType::F6E2M3 | DType::F6E3M2 | DType::F4 => 1,
68 }
69}
70
71fn out_elem_count(shapes: &[Shape]) -> u64 {
74 shapes
75 .last()
76 .map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
77 .unwrap_or(0)
78}
79
80fn input_elem_count(shapes: &[Shape]) -> u64 {
84 if shapes.len() <= 1 {
85 return 0;
86 }
87 shapes[..shapes.len() - 1]
88 .iter()
89 .map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
90 .sum()
91}
92
93pub fn cost_elementwise_unary_cpu(
101 shapes: &[Shape],
102 dtypes: &[DType],
103 _params: &OpParams,
104 _caps: &BackendCapabilities,
105) -> CostEstimate {
106 let n = out_elem_count(shapes);
107 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
108 CostEstimate {
109 flops: n,
110 bytes_moved: 2 * n * dsize,
111 kernel_overhead_ns: 50,
112 }
113}
114
115pub fn cost_elementwise_unary_transcendental_cpu(
119 shapes: &[Shape],
120 dtypes: &[DType],
121 _params: &OpParams,
122 _caps: &BackendCapabilities,
123) -> CostEstimate {
124 let n = out_elem_count(shapes);
125 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
126 CostEstimate {
127 flops: 10 * n,
128 bytes_moved: 2 * n * dsize,
129 kernel_overhead_ns: 50,
130 }
131}
132
133pub fn cost_elementwise_binary_cpu(
137 shapes: &[Shape],
138 dtypes: &[DType],
139 _params: &OpParams,
140 _caps: &BackendCapabilities,
141) -> CostEstimate {
142 let n = out_elem_count(shapes);
143 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
144 CostEstimate {
145 flops: n,
146 bytes_moved: 3 * n * dsize,
147 kernel_overhead_ns: 50,
148 }
149}
150
151pub fn cost_comparison_cpu(
154 shapes: &[Shape],
155 dtypes: &[DType],
156 _params: &OpParams,
157 _caps: &BackendCapabilities,
158) -> CostEstimate {
159 let n = out_elem_count(shapes);
160 let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
161 CostEstimate {
162 flops: n,
163 bytes_moved: 2 * n * dsize_in + n,
165 kernel_overhead_ns: 50,
166 }
167}
168
169pub fn cost_where_cpu(
171 shapes: &[Shape],
172 dtypes: &[DType],
173 _params: &OpParams,
174 _caps: &BackendCapabilities,
175) -> CostEstimate {
176 let n = out_elem_count(shapes);
177 let dsize = dtypes.get(1).map(|d| dtype_bytes(*d)).unwrap_or(4);
178 CostEstimate {
179 flops: n,
180 bytes_moved: n + 3 * n * dsize, kernel_overhead_ns: 50,
182 }
183}
184
185pub fn cost_reduction_cpu(
189 shapes: &[Shape],
190 dtypes: &[DType],
191 _params: &OpParams,
192 _caps: &BackendCapabilities,
193) -> CostEstimate {
194 let n_in = input_elem_count(shapes);
195 let n_out = out_elem_count(shapes);
196 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
197 CostEstimate {
198 flops: n_in,
199 bytes_moved: (n_in + n_out) * dsize,
200 kernel_overhead_ns: 50,
201 }
202}
203
204pub fn cost_reduce_to_cpu(
209 shapes: &[Shape],
210 dtypes: &[DType],
211 _params: &OpParams,
212 _caps: &BackendCapabilities,
213) -> CostEstimate {
214 let n_in = input_elem_count(shapes);
215 let n_out = out_elem_count(shapes);
216 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
217 CostEstimate {
218 flops: n_in,
219 bytes_moved: (n_in + n_out) * dsize,
220 kernel_overhead_ns: 50,
221 }
222}
223
224pub fn cost_matmul_cpu(
228 _shapes: &[Shape],
229 dtypes: &[DType],
230 params: &OpParams,
231 _caps: &BackendCapabilities,
232) -> CostEstimate {
233 let (m, n, k, batch) = match params {
234 OpParams::Matmul {
235 m,
236 n,
237 k,
238 lhs_batch_dims,
239 ..
240 } => {
241 let batch: u64 = lhs_batch_dims
242 .iter()
243 .map(|&d| d as u64)
244 .product::<u64>()
245 .max(1);
246 (*m as u64, *n as u64, *k as u64, batch)
247 }
248 _ => return CostEstimate::default(),
249 };
250 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
251 let flops = 2 * batch * m * n * k;
252 let bytes_moved = batch * (m * k + k * n + m * n) * dsize;
253 CostEstimate {
254 flops,
255 bytes_moved,
256 kernel_overhead_ns: 100,
257 }
258}
259
260pub fn cost_fused_linear_primitive_cpu(
264 shapes: &[Shape],
265 dtypes: &[DType],
266 params: &OpParams,
267 caps: &BackendCapabilities,
268) -> CostEstimate {
269 let mm = cost_matmul_cpu(shapes, dtypes, params, caps);
270 let (m, n, batch) = match params {
271 OpParams::Matmul {
272 m,
273 n,
274 lhs_batch_dims,
275 ..
276 } => {
277 let batch: u64 = lhs_batch_dims
278 .iter()
279 .map(|&d| d as u64)
280 .product::<u64>()
281 .max(1);
282 (*m as u64, *n as u64, batch)
283 }
284 _ => return mm,
285 };
286 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
287 let bias_flops = batch * m * n;
288 let bias_bytes = n * dsize;
289 CostEstimate {
290 flops: mm.flops + bias_flops,
291 bytes_moved: mm.bytes_moved + bias_bytes,
292 kernel_overhead_ns: 100,
293 }
294}
295
296pub fn cost_cast_cpu(
301 shapes: &[Shape],
302 dtypes: &[DType],
303 _params: &OpParams,
304 _caps: &BackendCapabilities,
305) -> CostEstimate {
306 let n = out_elem_count(shapes);
307 let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
308 let dsize_out = dtypes.last().map(|d| dtype_bytes(*d)).unwrap_or(dsize_in);
309 CostEstimate {
310 flops: 0,
311 bytes_moved: n * (dsize_in + dsize_out),
312 kernel_overhead_ns: 50,
313 }
314}
315
316pub fn cost_scalar_op_cpu(
319 shapes: &[Shape],
320 dtypes: &[DType],
321 _params: &OpParams,
322 _caps: &BackendCapabilities,
323) -> CostEstimate {
324 let n = out_elem_count(shapes);
325 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
326 CostEstimate {
327 flops: 2 * n,
328 bytes_moved: 2 * n * dsize,
329 kernel_overhead_ns: 50,
330 }
331}
332
333pub fn cost_masked_fill_cpu(
335 shapes: &[Shape],
336 dtypes: &[DType],
337 _params: &OpParams,
338 _caps: &BackendCapabilities,
339) -> CostEstimate {
340 let n = out_elem_count(shapes);
341 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
342 CostEstimate {
343 flops: 0,
344 bytes_moved: n * dsize + n + n * dsize, kernel_overhead_ns: 50,
346 }
347}
348
349pub fn cost_shape_op_cpu(
353 shapes: &[Shape],
354 dtypes: &[DType],
355 _params: &OpParams,
356 _caps: &BackendCapabilities,
357) -> CostEstimate {
358 let n_in = input_elem_count(shapes);
359 let n_out = out_elem_count(shapes);
360 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
361 CostEstimate {
362 flops: 0,
363 bytes_moved: (n_in + n_out) * dsize,
364 kernel_overhead_ns: 50,
365 }
366}
367
368pub fn cost_concat_cpu(
371 shapes: &[Shape],
372 dtypes: &[DType],
373 _params: &OpParams,
374 _caps: &BackendCapabilities,
375) -> CostEstimate {
376 let n_in = input_elem_count(shapes);
377 let n_out = out_elem_count(shapes);
378 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
379 CostEstimate {
380 flops: 0,
381 bytes_moved: (n_in + n_out) * dsize,
382 kernel_overhead_ns: 50,
383 }
384}
385
386pub fn cost_indexing_cpu(
391 shapes: &[Shape],
392 dtypes: &[DType],
393 _params: &OpParams,
394 _caps: &BackendCapabilities,
395) -> CostEstimate {
396 let n_out = out_elem_count(shapes);
397 let dsize_data = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
398 let dsize_idx = dtypes.get(1).map(|d| dtype_bytes(*d)).unwrap_or(4);
399 CostEstimate {
400 flops: n_out, bytes_moved: 2 * n_out * dsize_data + n_out * dsize_idx,
402 kernel_overhead_ns: 50,
403 }
404}
405
406pub fn cost_argindex_cpu(
410 shapes: &[Shape],
411 dtypes: &[DType],
412 _params: &OpParams,
413 _caps: &BackendCapabilities,
414) -> CostEstimate {
415 let n_in = input_elem_count(shapes);
416 let n_out = out_elem_count(shapes);
417 let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
418 CostEstimate {
419 flops: n_in,
420 bytes_moved: n_in * dsize_in + n_out * 4,
421 kernel_overhead_ns: 50,
422 }
423}
424
425pub fn cost_conv2d_primitive_cpu(
428 _shapes: &[Shape],
429 dtypes: &[DType],
430 params: &OpParams,
431 _caps: &BackendCapabilities,
432) -> CostEstimate {
433 let (x_shape, w_shape, out_shape, groups) = match params {
434 OpParams::Conv2D {
435 x_shape,
436 w_shape,
437 out_shape,
438 groups,
439 ..
440 } => (x_shape, w_shape, out_shape, *groups as u64),
441 _ => return CostEstimate::default(),
442 };
443 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
444 let n = out_shape[0] as u64;
445 let cout = out_shape[1] as u64;
446 let h_out = out_shape[2] as u64;
447 let w_out = out_shape[3] as u64;
448 let cin_per_g = w_shape[1] as u64;
449 let kh = w_shape[2] as u64;
450 let kw = w_shape[3] as u64;
451 let conv_flops = 2 * n * cout * h_out * w_out * cin_per_g * kh * kw;
452 let elems_in = (x_shape[0] * x_shape[1] * x_shape[2] * x_shape[3]) as u64;
453 let elems_w = (w_shape[0] * w_shape[1] * w_shape[2] * w_shape[3]) as u64;
454 let elems_out = n * cout * h_out * w_out;
455 let _ = groups; CostEstimate {
457 flops: conv_flops,
458 bytes_moved: (elems_in + elems_w + elems_out) * dsize,
459 kernel_overhead_ns: 100,
460 }
461}
462
463pub fn cost_conv_transpose2d_primitive_cpu(
467 _shapes: &[Shape],
468 dtypes: &[DType],
469 params: &OpParams,
470 _caps: &BackendCapabilities,
471) -> CostEstimate {
472 let (x_shape, w_shape, out_shape, groups) = match params {
473 OpParams::ConvTranspose2D {
474 x_shape,
475 w_shape,
476 out_shape,
477 groups,
478 ..
479 } => (x_shape, w_shape, out_shape, *groups as u64),
480 _ => return CostEstimate::default(),
481 };
482 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
483 let n = out_shape[0] as u64;
484 let cout = out_shape[1] as u64;
485 let h_out = out_shape[2] as u64;
486 let w_out = out_shape[3] as u64;
487 let cin_per_g = (x_shape[1] as u64) / groups;
488 let kh = w_shape[2] as u64;
489 let kw = w_shape[3] as u64;
490 let flops = 2 * n * cout * h_out * w_out * cin_per_g * kh * kw;
491 let elems_in = (x_shape[0] * x_shape[1] * x_shape[2] * x_shape[3]) as u64;
492 let elems_w = (w_shape[0] * w_shape[1] * w_shape[2] * w_shape[3]) as u64;
493 let elems_out = n * cout * h_out * w_out;
494 CostEstimate {
495 flops,
496 bytes_moved: (elems_in + elems_w + elems_out) * dsize,
497 kernel_overhead_ns: 100,
498 }
499}
500
501pub fn cost_flash_attn_primitive_cpu(
504 _shapes: &[Shape],
505 dtypes: &[DType],
506 params: &OpParams,
507 _caps: &BackendCapabilities,
508) -> CostEstimate {
509 let (b, hq, _hkv, sq, sk, d) = match params {
510 OpParams::FlashAttn {
511 b,
512 hq,
513 hkv,
514 sq,
515 sk,
516 d,
517 ..
518 } => (
519 *b as u64,
520 *hq as u64,
521 *hkv as u64,
522 *sq as u64,
523 *sk as u64,
524 *d as u64,
525 ),
526 _ => return CostEstimate::default(),
527 };
528 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
529 let mm_flops = 4 * b * hq * sq * sk * d;
530 let sm_flops = 5 * b * hq * sq * sk;
531 let elems_qkv = b * hq * sq * d + 2 * b * hq * sk * d;
532 let elems_out = b * hq * sq * d;
533 CostEstimate {
534 flops: mm_flops + sm_flops,
535 bytes_moved: (elems_qkv + elems_out) * dsize,
536 kernel_overhead_ns: 200,
537 }
538}
539
540pub fn cost_flash_decoding_cuda(
562 _shapes: &[Shape],
563 dtypes: &[DType],
564 params: &OpParams,
565 _caps: &BackendCapabilities,
566) -> CostEstimate {
567 let (b, hq, sq, d, k_len) = match params {
568 OpParams::FlashAttn {
569 b,
570 hq,
571 sq,
572 d,
573 k_len,
574 ..
575 } => (*b as u64, *hq as u64, *sq as u64, *d as u64, *k_len as u64),
576 _ => return CostEstimate::default(),
577 };
578 if sq != 1 || d == 0 || d > 128 {
580 return CostEstimate {
581 flops: u64::MAX,
582 bytes_moved: u64::MAX,
583 kernel_overhead_ns: u32::MAX,
584 };
585 }
586 let dsize = dtypes.first().map(|dt| dtype_bytes(*dt)).unwrap_or(2);
587 let kl = k_len.max(1);
589 let mm_flops = 4 * b * hq * kl * d;
590 let sm_flops = 5 * b * hq * kl;
591 let elems = 2 * b * hq * d + 2 * b * hq * kl * d;
593 CostEstimate {
594 flops: mm_flops + sm_flops,
595 bytes_moved: elems * dsize,
596 kernel_overhead_ns: 200,
597 }
598}
599
600pub fn cost_paged_attn_primitive_cpu(
604 _shapes: &[Shape],
605 dtypes: &[DType],
606 params: &OpParams,
607 _caps: &BackendCapabilities,
608) -> CostEstimate {
609 let (b, hq, sq, d, block_size, num_blocks) = match params {
610 OpParams::PagedAttn {
611 b,
612 hq,
613 sq,
614 d,
615 block_size,
616 num_blocks,
617 ..
618 } => (
619 *b as u64,
620 *hq as u64,
621 *sq as u64,
622 *d as u64,
623 *block_size as u64,
624 *num_blocks as u64,
625 ),
626 _ => return CostEstimate::default(),
627 };
628 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
629 let sk_upper = block_size * num_blocks;
630 let mm_flops = 4 * b * hq * sq * sk_upper * d;
631 let elems_q = b * hq * sq * d;
632 let elems_kv = 2 * num_blocks * block_size * d;
633 let elems_out = elems_q;
634 CostEstimate {
635 flops: mm_flops,
636 bytes_moved: (elems_q + elems_kv + elems_out) * dsize,
637 kernel_overhead_ns: 200,
638 }
639}
640
641pub fn cost_softmax_last_dim_primitive_cpu(
644 _shapes: &[Shape],
645 dtypes: &[DType],
646 params: &OpParams,
647 _caps: &BackendCapabilities,
648) -> CostEstimate {
649 let (outer, last_dim) = match params {
650 OpParams::SoftmaxLastDim {
651 outer_count,
652 last_dim,
653 } => (*outer_count as u64, *last_dim as u64),
654 _ => return CostEstimate::default(),
655 };
656 let n = outer * last_dim;
657 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
658 CostEstimate {
659 flops: 5 * n, bytes_moved: 3 * n * dsize,
661 kernel_overhead_ns: 50,
662 }
663}
664
665pub fn cost_nf4_matmul_primitive_cpu(
669 _shapes: &[Shape],
670 dtypes: &[DType],
671 params: &OpParams,
672 _caps: &BackendCapabilities,
673) -> CostEstimate {
674 let (batch, m, n, k, block_size) = match params {
675 OpParams::Nf4Matmul {
676 batch,
677 m,
678 n,
679 k,
680 block_size,
681 } => (
682 *batch as u64,
683 *m as u64,
684 *n as u64,
685 *k as u64,
686 *block_size as u64,
687 ),
688 _ => return CostEstimate::default(),
689 };
690 if block_size == 0 || k % block_size != 0 {
691 return CostEstimate::default();
692 }
693 let flops = batch * m * n * k * 3;
694 let elem_bytes = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
695 let bytes_moved = batch * m * k * elem_bytes
696 + n * (k / 2)
697 + n * (k / block_size) * 4
698 + batch * m * n * elem_bytes;
699 CostEstimate {
700 flops,
701 bytes_moved,
702 kernel_overhead_ns: 50,
703 }
704}
705
706pub fn cost_ssd_chunk_scan_primitive_cpu(
711 _shapes: &[Shape],
712 _dtypes: &[DType],
713 params: &OpParams,
714 _caps: &BackendCapabilities,
715) -> CostEstimate {
716 let (batch, seqlen, heads, head_dim, state_dim) = match params {
717 OpParams::SsdChunkScan {
718 batch,
719 seqlen,
720 heads,
721 head_dim,
722 state_dim,
723 ..
724 } => (
725 *batch as u64,
726 *seqlen as u64,
727 *heads as u64,
728 *head_dim as u64,
729 *state_dim as u64,
730 ),
731 _ => return CostEstimate::default(),
732 };
733 let flops = batch * seqlen * heads * head_dim * state_dim * 8;
734 let bytes_moved = 2 * batch * seqlen * heads * head_dim * 4
735 + batch * seqlen * heads * 4
736 + 2 * batch * seqlen * heads * state_dim * 4
737 + heads * 4;
738 CostEstimate {
739 flops,
740 bytes_moved,
741 kernel_overhead_ns: 50,
742 }
743}
744
745pub fn cost_selective_scan_primitive_cpu(
749 _shapes: &[Shape],
750 _dtypes: &[DType],
751 params: &OpParams,
752 _caps: &BackendCapabilities,
753) -> CostEstimate {
754 let (batch, seqlen, dim, dstate) = match params {
755 OpParams::SelectiveScan {
756 batch,
757 seqlen,
758 dim,
759 dstate,
760 ..
761 } => (*batch as u64, *seqlen as u64, *dim as u64, *dstate as u64),
762 _ => return CostEstimate::default(),
763 };
764 let flops = batch * seqlen * dim * dstate * 16;
765 let bytes_moved = 2 * batch * seqlen * dim * 4
766 + 2 * batch * seqlen * dstate * 4
767 + dim * dstate * 4
768 + batch * seqlen * dim * 4;
769 CostEstimate {
770 flops,
771 bytes_moved,
772 kernel_overhead_ns: 50,
773 }
774}
775
776pub fn cost_causal_conv1d_primitive_cpu(
781 _shapes: &[Shape],
782 _dtypes: &[DType],
783 params: &OpParams,
784 _caps: &BackendCapabilities,
785) -> CostEstimate {
786 let (batch, channels, seq_in, seq_out, kernel, use_silu) = match params {
787 OpParams::CausalConv1d {
788 batch,
789 channels,
790 seq_in,
791 seq_out,
792 kernel,
793 use_silu,
794 } => (
795 *batch as u64,
796 *channels as u64,
797 *seq_in as u64,
798 *seq_out as u64,
799 *kernel as u64,
800 *use_silu,
801 ),
802 _ => return CostEstimate::default(),
803 };
804 let per_out_flops = 2 * kernel + if use_silu { 10 } else { 0 };
805 let flops = batch * channels * seq_out * per_out_flops;
806 let bytes_moved = batch * channels * seq_in * 4
807 + channels * kernel * 4
808 + channels * 4
809 + batch * channels * seq_out * 4;
810 CostEstimate {
811 flops,
812 bytes_moved,
813 kernel_overhead_ns: 50,
814 }
815}
816
817pub fn cost_fused_softmax_cross_entropy_primitive_cpu(
824 _shapes: &[Shape],
825 _dtypes: &[DType],
826 params: &OpParams,
827 _caps: &BackendCapabilities,
828) -> CostEstimate {
829 let (n_rows, vocab) = match params {
830 OpParams::FusedSoftmaxCrossEntropy { n_rows, vocab, .. } => (*n_rows as u64, *vocab as u64),
831 _ => return CostEstimate::default(),
832 };
833 let per_row_flops = 3 * vocab + 10;
839 let flops = n_rows * per_row_flops;
840 let bytes_moved = n_rows * vocab * 4 + n_rows * 8 + 4;
842 CostEstimate {
843 flops,
844 bytes_moved,
845 kernel_overhead_ns: 50,
846 }
847}
848
849pub fn cost_norm_last_dim_primitive_cpu(
853 _shapes: &[Shape],
854 dtypes: &[DType],
855 params: &OpParams,
856 _caps: &BackendCapabilities,
857) -> CostEstimate {
858 let (outer, last_dim) = match params {
859 OpParams::NormLastDim {
860 outer_count,
861 last_dim,
862 ..
863 } => (*outer_count as u64, *last_dim as u64),
864 _ => return CostEstimate::default(),
865 };
866 let n = outer * last_dim;
867 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
868 CostEstimate {
869 flops: 7 * n, bytes_moved: 3 * n * dsize,
871 kernel_overhead_ns: 50,
872 }
873}
874
875pub fn cost_rope_primitive_cpu(
877 _shapes: &[Shape],
878 dtypes: &[DType],
879 params: &OpParams,
880 _caps: &BackendCapabilities,
881) -> CostEstimate {
882 let (outer, seq, head_dim) = match params {
883 OpParams::Rope {
884 outer_count,
885 seq,
886 head_dim,
887 } => (*outer_count as u64, *seq as u64, *head_dim as u64),
888 _ => return CostEstimate::default(),
889 };
890 let n = outer * seq * head_dim;
891 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
892 CostEstimate {
893 flops: 4 * n, bytes_moved: 2 * n * dsize + 2 * seq * head_dim * dsize, kernel_overhead_ns: 50,
896 }
897}
898
899pub fn cost_qmatmul_primitive_cpu(
902 _shapes: &[Shape],
903 dtypes: &[DType],
904 params: &OpParams,
905 _caps: &BackendCapabilities,
906) -> CostEstimate {
907 let (batch_count, m, n, k) = match params {
908 OpParams::QMatMul {
909 batch_count,
910 m,
911 n,
912 k,
913 ..
914 } => (*batch_count as u64, *m as u64, *n as u64, *k as u64),
915 _ => return CostEstimate::default(),
916 };
917 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
918 let flops = 2 * batch_count * m * n * k;
919 let bytes_moved = batch_count * (m * k + m * n) * dsize + n * k / 2; CostEstimate {
921 flops,
922 bytes_moved,
923 kernel_overhead_ns: 100,
924 }
925}
926
927pub fn cost_reduce_max_to_backward_primitive_cpu(
930 shapes: &[Shape],
931 dtypes: &[DType],
932 _params: &OpParams,
933 _caps: &BackendCapabilities,
934) -> CostEstimate {
935 let n_in = shapes
936 .first()
937 .map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
938 .unwrap_or(0);
939 let n_out = shapes
940 .get(1)
941 .map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
942 .unwrap_or(0);
943 let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
944 CostEstimate {
945 flops: 5 * n_in + 2 * n_out,
946 bytes_moved: (5 * n_in + 3 * n_out) * dsize,
947 kernel_overhead_ns: 80,
948 }
949}
950
951pub fn default_cost_for_op_kind(op: OpKind) -> CostFn {
965 use OpKind::*;
966 match op {
967 ReluElementwise | NegElementwise | SqrElementwise | SqrtElementwise | RecipElementwise
969 | AbsElementwise | StepElementwise | FloorElementwise | CeilElementwise
970 | RoundElementwise | SignElementwise => cost_elementwise_unary_cpu,
971
972 TanhElementwise | ExpElementwise | LogElementwise | SinElementwise | CosElementwise
974 | SigmoidElementwise | SiluElementwise | GeluTanhElementwise | GeluElementwise
975 | ErfElementwise | RsqrtElementwise => cost_elementwise_unary_transcendental_cpu,
976
977 AddElementwise | SubElementwise | MulElementwise | DivElementwise | MaximumElementwise
979 | MinimumElementwise | PowElementwise | RemElementwise => cost_elementwise_binary_cpu,
980
981 EqualElementwise
983 | NotEqualElementwise
984 | LessElementwise
985 | LessEqualElementwise
986 | GreaterElementwise
987 | GreaterEqualElementwise => cost_comparison_cpu,
988
989 Where => cost_where_cpu,
990
991 SumReduce | MaxReduce | MinReduce | MeanReduce => cost_reduction_cpu,
993 ReduceSumTo | ReduceMaxTo => cost_reduce_to_cpu,
994
995 MatMul => cost_matmul_cpu,
997 FusedLinear => cost_fused_linear_primitive_cpu,
998 QMatMul => cost_qmatmul_primitive_cpu,
999
1000 Conv2D => cost_conv2d_primitive_cpu,
1002 ConvTranspose2D => cost_conv_transpose2d_primitive_cpu,
1003
1004 FlashAttn | FlashAttnBackwardQ | FlashAttnBackwardK | FlashAttnBackwardV => {
1008 cost_flash_attn_primitive_cpu
1009 }
1010 PagedAttn => cost_paged_attn_primitive_cpu,
1011
1012 Cast => cost_cast_cpu,
1014
1015 Affine | ClampElementwise | PowIElementwise => cost_scalar_op_cpu,
1017
1018 Flip | Roll | Triu | Tril | CumSum | Pad | PadBackward => cost_shape_op_cpu,
1020
1021 MaskedFill => cost_masked_fill_cpu,
1023
1024 Concat => cost_concat_cpu,
1026
1027 SoftmaxLastDim | SoftmaxLastDimBackward | LogSoftmaxLastDim | LogSoftmaxLastDimBackward => {
1029 cost_softmax_last_dim_primitive_cpu
1030 }
1031 FusedSoftmaxCrossEntropy => cost_fused_softmax_cross_entropy_primitive_cpu,
1032 CausalConv1d => cost_causal_conv1d_primitive_cpu,
1033 SelectiveScan => cost_selective_scan_primitive_cpu,
1034 SsdChunkScan => cost_ssd_chunk_scan_primitive_cpu,
1035 Nf4Matmul => cost_nf4_matmul_primitive_cpu,
1036 RmsNormLastDim | RmsNormLastDimBackward | LayerNormLastDim | LayerNormLastDimBackward => {
1037 cost_norm_last_dim_primitive_cpu
1038 }
1039 ReduceMaxToBackward => cost_reduce_max_to_backward_primitive_cpu,
1040
1041 Rope => cost_rope_primitive_cpu,
1043
1044 IndexSelect | Gather | IndexAdd | ScatterAdd => cost_indexing_cpu,
1046
1047 ArgMaxDim | ArgMinDim => cost_argindex_cpu,
1049
1050 Copy => cost_shape_op_cpu,
1058
1059 WriteSlice => cost_shape_op_cpu,
1065
1066 WriteSliceRotating => cost_shape_op_cpu,
1071
1072 WriteSliceDoff => cost_shape_op_cpu,
1076
1077 _ => unknown_cost,
1083 }
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088 use super::*;
1089
1090 #[test]
1095 fn elementwise_unary_cost_scales_with_elem_count() {
1096 use fuel_ir::DeviceLocation;
1097 use fuel_ir::backend::{BackendCapabilities, TransferPath};
1098 use fuel_ir::probe::BackendId;
1099 use std::collections::HashSet;
1100
1101 let in_shape = Shape::from_dims(&[8, 16]);
1102 let out_shape = Shape::from_dims(&[8, 16]);
1103 let caps = BackendCapabilities {
1104 backend_id: BackendId::Cpu,
1105 device_location: DeviceLocation::Cpu,
1106 op_dtype_support: HashSet::new(),
1107 required_alignment: 1,
1108 access_granularity_bits: 8,
1109 transfer_paths: vec![(DeviceLocation::Cpu, TransferPath::SameDevice)],
1110 storage_substrate: fuel_ir::backend::SubstrateClass::HostBytes,
1111 compute_throughput_flops_per_ns: 1.0,
1112 mem_bandwidth_bytes_per_ns: 4.0,
1113 };
1114 let c = cost_elementwise_unary_cpu(
1115 &[in_shape, out_shape],
1116 &[DType::F32, DType::F32],
1117 &OpParams::None,
1118 &caps,
1119 );
1120 assert_eq!(c.flops, 128);
1122 assert_eq!(c.bytes_moved, 128 * 2 * 4);
1123 }
1124
1125 #[test]
1127 fn unknown_cost_returns_default() {
1128 use fuel_ir::DeviceLocation;
1129 use fuel_ir::backend::BackendCapabilities;
1130 use fuel_ir::probe::BackendId;
1131 use std::collections::HashSet;
1132
1133 let caps = BackendCapabilities {
1134 backend_id: BackendId::Cpu,
1135 device_location: DeviceLocation::Cpu,
1136 op_dtype_support: HashSet::new(),
1137 required_alignment: 1,
1138 access_granularity_bits: 8,
1139 transfer_paths: vec![],
1140 storage_substrate: fuel_ir::backend::SubstrateClass::HostBytes,
1141 compute_throughput_flops_per_ns: 1.0,
1142 mem_bandwidth_bytes_per_ns: 4.0,
1143 };
1144 let c = unknown_cost(&[], &[], &OpParams::None, &caps);
1145 assert_eq!(c, CostEstimate::default());
1146 }
1147}