use crate::fused::CostEstimate;
use crate::kernel::{CostFn, OpParams, unknown_cost};
use fuel_ir::backend::BackendCapabilities;
use fuel_ir::dispatch::OpKind;
use fuel_ir::{DType, Shape};
pub(crate) fn dtype_bytes(dt: DType) -> u64 {
match dt {
DType::F32 | DType::U32 | DType::I32 => 4,
DType::F64 | DType::I64 => 8,
DType::BF16 | DType::F16 | DType::I16 => 2,
DType::U8 | DType::I8 => 1,
DType::Bool => 1,
DType::F8E4M3 | DType::F8E5M2 | DType::F8E8M0 | DType::F8E6M2 => 1,
DType::F6E2M3 | DType::F6E3M2 | DType::F4 => 1,
}
}
fn out_elem_count(shapes: &[Shape]) -> u64 {
shapes
.last()
.map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
.unwrap_or(0)
}
fn input_elem_count(shapes: &[Shape]) -> u64 {
if shapes.len() <= 1 {
return 0;
}
shapes[..shapes.len() - 1]
.iter()
.map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
.sum()
}
pub fn cost_elementwise_unary_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n,
bytes_moved: 2 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_elementwise_unary_transcendental_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 10 * n,
bytes_moved: 2 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_elementwise_binary_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n,
bytes_moved: 3 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_comparison_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n,
bytes_moved: 2 * n * dsize_in + n,
kernel_overhead_ns: 50,
}
}
pub fn cost_where_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.get(1).map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n,
bytes_moved: n + 3 * n * dsize, kernel_overhead_ns: 50,
}
}
pub fn cost_reduction_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = input_elem_count(shapes);
let n_out = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n_in,
bytes_moved: (n_in + n_out) * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_reduce_to_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = input_elem_count(shapes);
let n_out = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n_in,
bytes_moved: (n_in + n_out) * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_matmul_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (m, n, k, batch) = match params {
OpParams::Matmul {
m,
n,
k,
lhs_batch_dims,
..
} => {
let batch: u64 = lhs_batch_dims
.iter()
.map(|&d| d as u64)
.product::<u64>()
.max(1);
(*m as u64, *n as u64, *k as u64, batch)
}
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let flops = 2 * batch * m * n * k;
let bytes_moved = batch * (m * k + k * n + m * n) * dsize;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 100,
}
}
pub fn cost_fused_linear_primitive_cpu(
shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
caps: &BackendCapabilities,
) -> CostEstimate {
let mm = cost_matmul_cpu(shapes, dtypes, params, caps);
let (m, n, batch) = match params {
OpParams::Matmul {
m,
n,
lhs_batch_dims,
..
} => {
let batch: u64 = lhs_batch_dims
.iter()
.map(|&d| d as u64)
.product::<u64>()
.max(1);
(*m as u64, *n as u64, batch)
}
_ => return mm,
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let bias_flops = batch * m * n;
let bias_bytes = n * dsize;
CostEstimate {
flops: mm.flops + bias_flops,
bytes_moved: mm.bytes_moved + bias_bytes,
kernel_overhead_ns: 100,
}
}
pub fn cost_cast_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let dsize_out = dtypes.last().map(|d| dtype_bytes(*d)).unwrap_or(dsize_in);
CostEstimate {
flops: 0,
bytes_moved: n * (dsize_in + dsize_out),
kernel_overhead_ns: 50,
}
}
pub fn cost_scalar_op_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 2 * n,
bytes_moved: 2 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_masked_fill_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 0,
bytes_moved: n * dsize + n + n * dsize, kernel_overhead_ns: 50,
}
}
pub fn cost_shape_op_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = input_elem_count(shapes);
let n_out = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 0,
bytes_moved: (n_in + n_out) * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_concat_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = input_elem_count(shapes);
let n_out = out_elem_count(shapes);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 0,
bytes_moved: (n_in + n_out) * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_indexing_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_out = out_elem_count(shapes);
let dsize_data = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let dsize_idx = dtypes.get(1).map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n_out, bytes_moved: 2 * n_out * dsize_data + n_out * dsize_idx,
kernel_overhead_ns: 50,
}
}
pub fn cost_argindex_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = input_elem_count(shapes);
let n_out = out_elem_count(shapes);
let dsize_in = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: n_in,
bytes_moved: n_in * dsize_in + n_out * 4,
kernel_overhead_ns: 50,
}
}
pub fn cost_conv2d_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (x_shape, w_shape, out_shape, groups) = match params {
OpParams::Conv2D {
x_shape,
w_shape,
out_shape,
groups,
..
} => (x_shape, w_shape, out_shape, *groups as u64),
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let n = out_shape[0] as u64;
let cout = out_shape[1] as u64;
let h_out = out_shape[2] as u64;
let w_out = out_shape[3] as u64;
let cin_per_g = w_shape[1] as u64;
let kh = w_shape[2] as u64;
let kw = w_shape[3] as u64;
let conv_flops = 2 * n * cout * h_out * w_out * cin_per_g * kh * kw;
let elems_in = (x_shape[0] * x_shape[1] * x_shape[2] * x_shape[3]) as u64;
let elems_w = (w_shape[0] * w_shape[1] * w_shape[2] * w_shape[3]) as u64;
let elems_out = n * cout * h_out * w_out;
let _ = groups; CostEstimate {
flops: conv_flops,
bytes_moved: (elems_in + elems_w + elems_out) * dsize,
kernel_overhead_ns: 100,
}
}
pub fn cost_conv_transpose2d_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (x_shape, w_shape, out_shape, groups) = match params {
OpParams::ConvTranspose2D {
x_shape,
w_shape,
out_shape,
groups,
..
} => (x_shape, w_shape, out_shape, *groups as u64),
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let n = out_shape[0] as u64;
let cout = out_shape[1] as u64;
let h_out = out_shape[2] as u64;
let w_out = out_shape[3] as u64;
let cin_per_g = (x_shape[1] as u64) / groups;
let kh = w_shape[2] as u64;
let kw = w_shape[3] as u64;
let flops = 2 * n * cout * h_out * w_out * cin_per_g * kh * kw;
let elems_in = (x_shape[0] * x_shape[1] * x_shape[2] * x_shape[3]) as u64;
let elems_w = (w_shape[0] * w_shape[1] * w_shape[2] * w_shape[3]) as u64;
let elems_out = n * cout * h_out * w_out;
CostEstimate {
flops,
bytes_moved: (elems_in + elems_w + elems_out) * dsize,
kernel_overhead_ns: 100,
}
}
pub fn cost_flash_attn_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (b, hq, _hkv, sq, sk, d) = match params {
OpParams::FlashAttn {
b,
hq,
hkv,
sq,
sk,
d,
..
} => (
*b as u64,
*hq as u64,
*hkv as u64,
*sq as u64,
*sk as u64,
*d as u64,
),
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let mm_flops = 4 * b * hq * sq * sk * d;
let sm_flops = 5 * b * hq * sq * sk;
let elems_qkv = b * hq * sq * d + 2 * b * hq * sk * d;
let elems_out = b * hq * sq * d;
CostEstimate {
flops: mm_flops + sm_flops,
bytes_moved: (elems_qkv + elems_out) * dsize,
kernel_overhead_ns: 200,
}
}
pub fn cost_flash_decoding_cuda(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (b, hq, sq, d, k_len) = match params {
OpParams::FlashAttn {
b,
hq,
sq,
d,
k_len,
..
} => (*b as u64, *hq as u64, *sq as u64, *d as u64, *k_len as u64),
_ => return CostEstimate::default(),
};
if sq != 1 || d == 0 || d > 128 {
return CostEstimate {
flops: u64::MAX,
bytes_moved: u64::MAX,
kernel_overhead_ns: u32::MAX,
};
}
let dsize = dtypes.first().map(|dt| dtype_bytes(*dt)).unwrap_or(2);
let kl = k_len.max(1);
let mm_flops = 4 * b * hq * kl * d;
let sm_flops = 5 * b * hq * kl;
let elems = 2 * b * hq * d + 2 * b * hq * kl * d;
CostEstimate {
flops: mm_flops + sm_flops,
bytes_moved: elems * dsize,
kernel_overhead_ns: 200,
}
}
pub fn cost_paged_attn_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (b, hq, sq, d, block_size, num_blocks) = match params {
OpParams::PagedAttn {
b,
hq,
sq,
d,
block_size,
num_blocks,
..
} => (
*b as u64,
*hq as u64,
*sq as u64,
*d as u64,
*block_size as u64,
*num_blocks as u64,
),
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let sk_upper = block_size * num_blocks;
let mm_flops = 4 * b * hq * sq * sk_upper * d;
let elems_q = b * hq * sq * d;
let elems_kv = 2 * num_blocks * block_size * d;
let elems_out = elems_q;
CostEstimate {
flops: mm_flops,
bytes_moved: (elems_q + elems_kv + elems_out) * dsize,
kernel_overhead_ns: 200,
}
}
pub fn cost_softmax_last_dim_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (outer, last_dim) = match params {
OpParams::SoftmaxLastDim {
outer_count,
last_dim,
} => (*outer_count as u64, *last_dim as u64),
_ => return CostEstimate::default(),
};
let n = outer * last_dim;
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 5 * n, bytes_moved: 3 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_nf4_matmul_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (batch, m, n, k, block_size) = match params {
OpParams::Nf4Matmul {
batch,
m,
n,
k,
block_size,
} => (
*batch as u64,
*m as u64,
*n as u64,
*k as u64,
*block_size as u64,
),
_ => return CostEstimate::default(),
};
if block_size == 0 || k % block_size != 0 {
return CostEstimate::default();
}
let flops = batch * m * n * k * 3;
let elem_bytes = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let bytes_moved = batch * m * k * elem_bytes
+ n * (k / 2)
+ n * (k / block_size) * 4
+ batch * m * n * elem_bytes;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 50,
}
}
pub fn cost_ssd_chunk_scan_primitive_cpu(
_shapes: &[Shape],
_dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (batch, seqlen, heads, head_dim, state_dim) = match params {
OpParams::SsdChunkScan {
batch,
seqlen,
heads,
head_dim,
state_dim,
..
} => (
*batch as u64,
*seqlen as u64,
*heads as u64,
*head_dim as u64,
*state_dim as u64,
),
_ => return CostEstimate::default(),
};
let flops = batch * seqlen * heads * head_dim * state_dim * 8;
let bytes_moved = 2 * batch * seqlen * heads * head_dim * 4
+ batch * seqlen * heads * 4
+ 2 * batch * seqlen * heads * state_dim * 4
+ heads * 4;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 50,
}
}
pub fn cost_selective_scan_primitive_cpu(
_shapes: &[Shape],
_dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (batch, seqlen, dim, dstate) = match params {
OpParams::SelectiveScan {
batch,
seqlen,
dim,
dstate,
..
} => (*batch as u64, *seqlen as u64, *dim as u64, *dstate as u64),
_ => return CostEstimate::default(),
};
let flops = batch * seqlen * dim * dstate * 16;
let bytes_moved = 2 * batch * seqlen * dim * 4
+ 2 * batch * seqlen * dstate * 4
+ dim * dstate * 4
+ batch * seqlen * dim * 4;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 50,
}
}
pub fn cost_causal_conv1d_primitive_cpu(
_shapes: &[Shape],
_dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (batch, channels, seq_in, seq_out, kernel, use_silu) = match params {
OpParams::CausalConv1d {
batch,
channels,
seq_in,
seq_out,
kernel,
use_silu,
} => (
*batch as u64,
*channels as u64,
*seq_in as u64,
*seq_out as u64,
*kernel as u64,
*use_silu,
),
_ => return CostEstimate::default(),
};
let per_out_flops = 2 * kernel + if use_silu { 10 } else { 0 };
let flops = batch * channels * seq_out * per_out_flops;
let bytes_moved = batch * channels * seq_in * 4
+ channels * kernel * 4
+ channels * 4
+ batch * channels * seq_out * 4;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 50,
}
}
pub fn cost_fused_softmax_cross_entropy_primitive_cpu(
_shapes: &[Shape],
_dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (n_rows, vocab) = match params {
OpParams::FusedSoftmaxCrossEntropy { n_rows, vocab, .. } => (*n_rows as u64, *vocab as u64),
_ => return CostEstimate::default(),
};
let per_row_flops = 3 * vocab + 10;
let flops = n_rows * per_row_flops;
let bytes_moved = n_rows * vocab * 4 + n_rows * 8 + 4;
CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 50,
}
}
pub fn cost_norm_last_dim_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (outer, last_dim) = match params {
OpParams::NormLastDim {
outer_count,
last_dim,
..
} => (*outer_count as u64, *last_dim as u64),
_ => return CostEstimate::default(),
};
let n = outer * last_dim;
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 7 * n, bytes_moved: 3 * n * dsize,
kernel_overhead_ns: 50,
}
}
pub fn cost_rope_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (outer, seq, head_dim) = match params {
OpParams::Rope {
outer_count,
seq,
head_dim,
} => (*outer_count as u64, *seq as u64, *head_dim as u64),
_ => return CostEstimate::default(),
};
let n = outer * seq * head_dim;
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 4 * n, bytes_moved: 2 * n * dsize + 2 * seq * head_dim * dsize, kernel_overhead_ns: 50,
}
}
pub fn cost_qmatmul_primitive_cpu(
_shapes: &[Shape],
dtypes: &[DType],
params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let (batch_count, m, n, k) = match params {
OpParams::QMatMul {
batch_count,
m,
n,
k,
..
} => (*batch_count as u64, *m as u64, *n as u64, *k as u64),
_ => return CostEstimate::default(),
};
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
let flops = 2 * batch_count * m * n * k;
let bytes_moved = batch_count * (m * k + m * n) * dsize + n * k / 2; CostEstimate {
flops,
bytes_moved,
kernel_overhead_ns: 100,
}
}
pub fn cost_reduce_max_to_backward_primitive_cpu(
shapes: &[Shape],
dtypes: &[DType],
_params: &OpParams,
_caps: &BackendCapabilities,
) -> CostEstimate {
let n_in = shapes
.first()
.map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
.unwrap_or(0);
let n_out = shapes
.get(1)
.map(|s| s.dims().iter().map(|&d| d as u64).product::<u64>())
.unwrap_or(0);
let dsize = dtypes.first().map(|d| dtype_bytes(*d)).unwrap_or(4);
CostEstimate {
flops: 5 * n_in + 2 * n_out,
bytes_moved: (5 * n_in + 3 * n_out) * dsize,
kernel_overhead_ns: 80,
}
}
pub fn default_cost_for_op_kind(op: OpKind) -> CostFn {
use OpKind::*;
match op {
ReluElementwise | NegElementwise | SqrElementwise | SqrtElementwise | RecipElementwise
| AbsElementwise | StepElementwise | FloorElementwise | CeilElementwise
| RoundElementwise | SignElementwise => cost_elementwise_unary_cpu,
TanhElementwise | ExpElementwise | LogElementwise | SinElementwise | CosElementwise
| SigmoidElementwise | SiluElementwise | GeluTanhElementwise | GeluElementwise
| ErfElementwise | RsqrtElementwise => cost_elementwise_unary_transcendental_cpu,
AddElementwise | SubElementwise | MulElementwise | DivElementwise | MaximumElementwise
| MinimumElementwise | PowElementwise | RemElementwise => cost_elementwise_binary_cpu,
EqualElementwise
| NotEqualElementwise
| LessElementwise
| LessEqualElementwise
| GreaterElementwise
| GreaterEqualElementwise => cost_comparison_cpu,
Where => cost_where_cpu,
SumReduce | MaxReduce | MinReduce | MeanReduce => cost_reduction_cpu,
ReduceSumTo | ReduceMaxTo => cost_reduce_to_cpu,
MatMul => cost_matmul_cpu,
FusedLinear => cost_fused_linear_primitive_cpu,
QMatMul => cost_qmatmul_primitive_cpu,
Conv2D => cost_conv2d_primitive_cpu,
ConvTranspose2D => cost_conv_transpose2d_primitive_cpu,
FlashAttn | FlashAttnBackwardQ | FlashAttnBackwardK | FlashAttnBackwardV => {
cost_flash_attn_primitive_cpu
}
PagedAttn => cost_paged_attn_primitive_cpu,
Cast => cost_cast_cpu,
Affine | ClampElementwise | PowIElementwise => cost_scalar_op_cpu,
Flip | Roll | Triu | Tril | CumSum | Pad | PadBackward => cost_shape_op_cpu,
MaskedFill => cost_masked_fill_cpu,
Concat => cost_concat_cpu,
SoftmaxLastDim | SoftmaxLastDimBackward | LogSoftmaxLastDim | LogSoftmaxLastDimBackward => {
cost_softmax_last_dim_primitive_cpu
}
FusedSoftmaxCrossEntropy => cost_fused_softmax_cross_entropy_primitive_cpu,
CausalConv1d => cost_causal_conv1d_primitive_cpu,
SelectiveScan => cost_selective_scan_primitive_cpu,
SsdChunkScan => cost_ssd_chunk_scan_primitive_cpu,
Nf4Matmul => cost_nf4_matmul_primitive_cpu,
RmsNormLastDim | RmsNormLastDimBackward | LayerNormLastDim | LayerNormLastDimBackward => {
cost_norm_last_dim_primitive_cpu
}
ReduceMaxToBackward => cost_reduce_max_to_backward_primitive_cpu,
Rope => cost_rope_primitive_cpu,
IndexSelect | Gather | IndexAdd | ScatterAdd => cost_indexing_cpu,
ArgMaxDim | ArgMinDim => cost_argindex_cpu,
Copy => cost_shape_op_cpu,
WriteSlice => cost_shape_op_cpu,
WriteSliceRotating => cost_shape_op_cpu,
WriteSliceDoff => cost_shape_op_cpu,
_ => unknown_cost,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn elementwise_unary_cost_scales_with_elem_count() {
use fuel_ir::DeviceLocation;
use fuel_ir::backend::{BackendCapabilities, TransferPath};
use fuel_ir::probe::BackendId;
use std::collections::HashSet;
let in_shape = Shape::from_dims(&[8, 16]);
let out_shape = Shape::from_dims(&[8, 16]);
let caps = BackendCapabilities {
backend_id: BackendId::Cpu,
device_location: DeviceLocation::Cpu,
op_dtype_support: HashSet::new(),
required_alignment: 1,
access_granularity_bits: 8,
transfer_paths: vec![(DeviceLocation::Cpu, TransferPath::SameDevice)],
storage_substrate: fuel_ir::backend::SubstrateClass::HostBytes,
compute_throughput_flops_per_ns: 1.0,
mem_bandwidth_bytes_per_ns: 4.0,
};
let c = cost_elementwise_unary_cpu(
&[in_shape, out_shape],
&[DType::F32, DType::F32],
&OpParams::None,
&caps,
);
assert_eq!(c.flops, 128);
assert_eq!(c.bytes_moved, 128 * 2 * 4);
}
#[test]
fn unknown_cost_returns_default() {
use fuel_ir::DeviceLocation;
use fuel_ir::backend::BackendCapabilities;
use fuel_ir::probe::BackendId;
use std::collections::HashSet;
let caps = BackendCapabilities {
backend_id: BackendId::Cpu,
device_location: DeviceLocation::Cpu,
op_dtype_support: HashSet::new(),
required_alignment: 1,
access_granularity_bits: 8,
transfer_paths: vec![],
storage_substrate: fuel_ir::backend::SubstrateClass::HostBytes,
compute_throughput_flops_per_ns: 1.0,
mem_bandwidth_bytes_per_ns: 4.0,
};
let c = unknown_cost(&[], &[], &OpParams::None, &caps);
assert_eq!(c, CostEstimate::default());
}
}