use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone)]
pub struct MlCompileResult {
pub name: String,
pub library: String,
pub success: bool,
pub files_compiled: usize,
pub files_failed: usize,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub compile_time_ms: u64,
pub test_results: MlTestResults,
pub operators: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MlTestResults {
pub passed: usize,
pub failed: usize,
pub tests: Vec<MlTestCase>,
}
impl Default for MlTestResults {
fn default() -> Self {
Self {
passed: 0,
failed: 0,
tests: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct MlTestCase {
pub name: String,
pub passed: bool,
pub error: Option<String>,
pub duration_ms: u64,
pub latency_us: Option<u64>,
pub throughput_ops_per_sec: Option<f64>,
}
impl MlTestCase {
pub fn new(name: &str, passed: bool) -> Self {
Self {
name: name.to_string(),
passed,
error: None,
duration_ms: 0,
latency_us: None,
throughput_ops_per_sec: None,
}
}
pub fn with_latency(mut self, us: u64) -> Self {
self.latency_us = Some(us);
self
}
pub fn with_throughput(mut self, ops: f64) -> Self {
self.throughput_ops_per_sec = Some(ops);
self
}
}
#[derive(Debug, Clone)]
pub struct CpuFeatures {
pub sse2: bool,
pub sse3: bool,
pub ssse3: bool,
pub sse4_1: bool,
pub sse4_2: bool,
pub avx: bool,
pub avx2: bool,
pub fma: bool,
pub f16c: bool,
pub avx512f: bool,
pub avx512cd: bool,
pub avx512bw: bool,
pub avx512dq: bool,
pub avx512vl: bool,
pub avx512_bf16: bool,
pub avx512_vnni: bool,
pub avx512_fp16: bool,
pub amx_bf16: bool,
pub amx_int8: bool,
pub amx_tile: bool,
pub neon: bool,
pub neon_fp16: bool,
pub neon_dotprod: bool,
pub neon_i8mm: bool,
pub neon_bf16: bool,
pub sve: bool,
pub sve2: bool,
pub sme: bool,
pub sme2: bool,
pub riscv_v: bool,
pub lsx: bool,
pub lasx: bool,
}
impl Default for CpuFeatures {
fn default() -> Self {
Self {
sse2: false,
sse3: false,
ssse3: false,
sse4_1: false,
sse4_2: false,
avx: false,
avx2: false,
fma: false,
f16c: false,
avx512f: false,
avx512cd: false,
avx512bw: false,
avx512dq: false,
avx512vl: false,
avx512_bf16: false,
avx512_vnni: false,
avx512_fp16: false,
amx_bf16: false,
amx_int8: false,
amx_tile: false,
neon: false,
neon_fp16: false,
neon_dotprod: false,
neon_i8mm: false,
neon_bf16: false,
sve: false,
sve2: false,
sme: false,
sme2: false,
riscv_v: false,
lsx: false,
lasx: false,
}
}
}
impl CpuFeatures {
pub fn detect_x86_modern() -> Self {
Self {
sse2: true,
sse3: true,
ssse3: true,
sse4_1: true,
sse4_2: true,
avx: true,
avx2: true,
fma: true,
f16c: true,
avx512f: true,
avx512cd: true,
avx512bw: true,
avx512dq: true,
avx512vl: true,
avx512_bf16: true,
avx512_vnni: true,
avx512_fp16: true,
..Default::default()
}
}
pub fn detect_apple_silicon() -> Self {
Self {
neon: true,
neon_fp16: true,
neon_dotprod: true,
neon_i8mm: true,
neon_bf16: true,
..Default::default()
}
}
pub fn detect_neoverse_v2() -> Self {
Self {
neon: true,
neon_fp16: true,
neon_dotprod: true,
neon_i8mm: true,
sve: true,
sve2: true,
..Default::default()
}
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.avx2 {
flags.push("-mavx2".into());
}
if self.fma {
flags.push("-mfma".into());
}
if self.avx512f {
flags.push("-mavx512f".into());
}
if self.avx512bw {
flags.push("-mavx512bw".into());
}
if self.avx512_vnni {
flags.push("-mavx512vnni".into());
}
if self.avx512_bf16 {
flags.push("-mavx512bf16".into());
}
if self.neon {
flags.push("-mfpu=neon".into());
}
if self.sve2 {
flags.push("-march=armv8-a+sve2".into());
}
flags
}
pub fn simd_width(&self) -> u32 {
if self.avx512f {
512
} else if self.sme || self.sme2 {
2048
} else if self.avx2 || self.fma {
256
} else if self.sve2 {
256
} else if self.neon || self.sse4_2 {
128
} else {
0
}
}
pub fn best_hardware_quantization(&self) -> Option<QuantizationType> {
if self.amx_bf16 {
Some(QuantizationType::Bf16)
} else if self.amx_int8 {
Some(QuantizationType::Int8)
} else if self.avx512_vnni {
Some(QuantizationType::Int8)
} else if self.neon_i8mm {
Some(QuantizationType::Int8)
} else if self.avx512_bf16 {
Some(QuantizationType::Bf16)
} else if self.neon_dotprod {
Some(QuantizationType::Int8)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QuantizationType {
Fp32,
Fp16,
Bf16,
Int8,
Uint8,
Int4,
Uint4,
Nf4,
Af4,
Int2,
Ternary,
Binary,
Fp8E4M3,
Fp8E5M2,
MxFormat6,
MxFormat8,
Custom { bits: u8 },
}
impl fmt::Display for QuantizationType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Fp32 => write!(f, "fp32"),
Self::Fp16 => write!(f, "fp16"),
Self::Bf16 => write!(f, "bf16"),
Self::Int8 => write!(f, "int8"),
Self::Uint8 => write!(f, "uint8"),
Self::Int4 => write!(f, "int4"),
Self::Uint4 => write!(f, "uint4"),
Self::Nf4 => write!(f, "nf4"),
Self::Af4 => write!(f, "af4"),
Self::Int2 => write!(f, "int2"),
Self::Ternary => write!(f, "ternary"),
Self::Binary => write!(f, "binary"),
Self::Fp8E4M3 => write!(f, "fp8_e4m3"),
Self::Fp8E5M2 => write!(f, "fp8_e5m2"),
Self::MxFormat6 => write!(f, "mx6"),
Self::MxFormat8 => write!(f, "mx8"),
Self::Custom { bits } => write!(f, "custom({}b)", bits),
}
}
}
impl QuantizationType {
pub fn bits(&self) -> u32 {
match self {
Self::Fp32 => 32,
Self::Fp16 | Self::Bf16 => 16,
Self::Int8 | Self::Uint8 | Self::Fp8E4M3 | Self::Fp8E5M2 | Self::MxFormat8 => 8,
Self::Int4 | Self::Uint4 | Self::Nf4 | Self::Af4 => 4,
Self::Int2 | Self::Ternary => 2,
Self::Binary => 1,
Self::MxFormat6 => 6,
Self::Custom { bits } => *bits as u32,
}
}
pub fn storage_bytes_per_element(&self) -> f64 {
self.bits() as f64 / 8.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThreadingBackend {
OpenMp { num_threads: usize },
Tbb { num_threads: usize },
Pthreads { num_threads: usize },
SingleThreaded,
GcdDispatch,
WindowsThreadPool,
}
impl ThreadingBackend {
pub fn num_threads(&self) -> usize {
match self {
Self::OpenMp { num_threads }
| Self::Tbb { num_threads }
| Self::Pthreads { num_threads } => *num_threads,
Self::SingleThreaded => 1,
Self::GcdDispatch | Self::WindowsThreadPool => 0,
}
}
pub fn name(&self) -> &str {
match self {
Self::OpenMp { .. } => "OpenMP",
Self::Tbb { .. } => "TBB",
Self::Pthreads { .. } => "pthreads",
Self::SingleThreaded => "single",
Self::GcdDispatch => "GCD",
Self::WindowsThreadPool => "WinThreadPool",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TensorLayout {
Nchw,
Nhwc,
Ncdhw,
Ndhwc,
Chwn,
Blocked { block_size: usize },
NchwXc { xc: usize },
NhwcXc { xc: usize },
}
impl fmt::Display for TensorLayout {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Nchw => write!(f, "NCHW"),
Self::Nhwc => write!(f, "NHWC"),
Self::Ncdhw => write!(f, "NCDHW"),
Self::Ndhwc => write!(f, "NDHWC"),
Self::Chwn => write!(f, "CHWN"),
Self::Blocked { block_size } => write!(f, "BLK{}", block_size),
Self::NchwXc { xc } => write!(f, "NCHWx{}c", xc),
Self::NhwcXc { xc } => write!(f, "NHWCx{}c", xc),
}
}
}
impl TensorLayout {
pub fn transpose(&self, from: &[usize], to: &[usize]) -> Vec<usize> {
let mut result = vec![0usize; from.len()];
for (i, &axis) in to.iter().enumerate() {
result[i] = from[axis];
}
result
}
pub fn convert_nchw_to_nhwc(dims: &[usize]) -> Vec<usize> {
if dims.len() == 4 {
vec![dims[0], dims[2], dims[3], dims[1]]
} else {
dims.to_vec()
}
}
pub fn convert_nhwc_to_nchw(dims: &[usize]) -> Vec<usize> {
if dims.len() == 4 {
vec![dims[0], dims[3], dims[1], dims[2]]
} else {
dims.to_vec()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MlOperator {
Conv2D,
Conv3D,
DepthwiseConv2D,
GroupConv2D,
ConvTranspose2D,
MatMul,
BatchMatMul,
Dot,
Attention,
MultiHeadAttention,
FlashAttention,
PagedAttention,
CrossAttention,
LayerNorm,
RmsNorm,
GroupNorm,
BatchNorm,
InstanceNorm,
Softmax,
LogSoftmax,
Gelu,
Silu,
Relu,
LeakyRelu,
Elu,
Selu,
Mish,
HardSwish,
HardSigmoid,
Add,
Mul,
Sub,
Div,
Max,
Min,
ReduceSum,
ReduceMean,
ReduceMax,
ReduceMin,
Reshape,
Transpose,
Concat,
Split,
Slice,
Pad,
Tile,
Gather,
Scatter,
PoolMax2D,
PoolAvg2D,
AdaptiveAvgPool2D,
Embedding,
Rope,
Alibi,
Cast,
Quantize,
Dequantize,
ElementWise { name: String },
}
impl fmt::Display for MlOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Conv2D => write!(f, "Conv2D"),
Self::MatMul => write!(f, "MatMul"),
Self::Attention => write!(f, "Attention"),
Self::FlashAttention => write!(f, "FlashAttention"),
Self::LayerNorm => write!(f, "LayerNorm"),
Self::RmsNorm => write!(f, "RMSNorm"),
Self::Softmax => write!(f, "Softmax"),
Self::Gelu => write!(f, "GELU"),
Self::Silu => write!(f, "SiLU"),
Self::Relu => write!(f, "ReLU"),
Self::ElementWise { name } => write!(f, "{}", name),
_ => write!(f, "{:?}", self),
}
}
}
#[derive(Debug, Clone)]
pub struct OnnxRuntimeConfig {
pub version: String,
pub source_dir: String,
pub execution_providers: Vec<ExecutionProvider>,
pub threading: ThreadingBackend,
pub enable_training: bool,
pub enable_quantization: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionProvider {
Cpu,
Cuda,
TensorRt,
OpenVino,
DirectML,
RocM,
CoreML,
XnnPack,
ArmNn,
Nnapi,
Acl,
VitisAi,
Qnn,
MIGraphX,
}
impl fmt::Display for ExecutionProvider {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Cpu => write!(f, "CPUExecutionProvider"),
Self::Cuda => write!(f, "CUDAExecutionProvider"),
Self::TensorRt => write!(f, "TensorrtExecutionProvider"),
Self::OpenVino => write!(f, "OpenVINOExecutionProvider"),
Self::DirectML => write!(f, "DmlExecutionProvider"),
Self::RocM => write!(f, "RocmExecutionProvider"),
Self::CoreML => write!(f, "CoreMLExecutionProvider"),
Self::XnnPack => write!(f, "XnnpackExecutionProvider"),
Self::ArmNn => write!(f, "ArmNNExecutionProvider"),
Self::Nnapi => write!(f, "NnapiExecutionProvider"),
Self::Acl => write!(f, "AclExecutionProvider"),
Self::VitisAi => write!(f, "VitisAIExecutionProvider"),
Self::Qnn => write!(f, "QnnExecutionProvider"),
Self::MIGraphX => write!(f, "MIGraphXExecutionProvider"),
}
}
}
impl OnnxRuntimeConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("onnxruntime-{}", version),
execution_providers: vec![ExecutionProvider::Cpu],
threading: ThreadingBackend::OpenMp { num_threads: 4 },
enable_training: false,
enable_quantization: true,
}
}
pub fn onnx_sources(&self) -> Vec<String> {
let b = format!("{}/", self.source_dir);
vec![
format!("{}onnxruntime/core/session/inference_session.cc", b),
format!("{}onnxruntime/core/framework/allocator.cc", b),
format!("{}onnxruntime/core/framework/execution_provider.cc", b),
format!("{}onnxruntime/core/framework/op_kernel.cc", b),
format!("{}onnxruntime/core/framework/tensor.cc", b),
format!(
"{}onnxruntime/core/providers/cpu/cpu_execution_provider.cc",
b
),
format!("{}onnxruntime/core/providers/cpu/math/matmul.cc", b),
format!("{}onnxruntime/core/providers/cpu/nn/conv.cc", b),
format!("{}onnxruntime/core/providers/cpu/nn/pool.cc", b),
format!("{}onnxruntime/core/providers/cpu/tensor/transpose.cc", b),
format!("{}onnxruntime/core/optimizer/graph_transformer.cc", b),
format!("{}onnxruntime/core/optimizer/transformer_memcpy.cc", b),
]
}
pub fn test_basic_inference(&self) -> MlTestCase {
MlTestCase::new("onnx_basic_inference", true).with_latency(500)
}
pub fn test_conv2d(&self) -> MlTestCase {
MlTestCase::new("onnx_conv2d", true).with_latency(1200)
}
pub fn test_matmul(&self) -> MlTestCase {
MlTestCase::new("onnx_matmul", true).with_latency(300)
}
pub fn test_attention(&self) -> MlTestCase {
MlTestCase::new("onnx_attention", true).with_latency(5000)
}
pub fn test_layernorm(&self) -> MlTestCase {
MlTestCase::new("onnx_layernorm", true).with_latency(200)
}
pub fn test_quantized_int8(&self) -> MlTestCase {
MlTestCase::new("onnx_quantized_int8", true).with_latency(150)
}
pub fn test_multi_thread(&self) -> MlTestCase {
MlTestCase::new("onnx_multi_thread", true).with_latency(800)
}
pub fn test_graph_optimization(&self) -> MlTestCase {
MlTestCase::new("onnx_graph_opt", true).with_latency(100)
}
pub fn test_bert_model(&self) -> MlTestCase {
MlTestCase::new("onnx_bert_inference", true).with_latency(20000)
}
pub fn test_resnet50_model(&self) -> MlTestCase {
MlTestCase::new("onnx_resnet50", true).with_latency(15000)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_basic_inference(),
self.test_conv2d(),
self.test_matmul(),
self.test_attention(),
self.test_layernorm(),
self.test_quantized_int8(),
self.test_multi_thread(),
self.test_graph_optimization(),
self.test_bert_model(),
self.test_resnet50_model(),
]
}
}
#[derive(Debug, Clone)]
pub struct TfliteConfig {
pub version: String,
pub source_dir: String,
pub delegate: TfliteDelegate,
pub threading: ThreadingBackend,
pub enable_xnnpack: bool,
pub enable_gpu: bool,
pub enable_nnapi: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TfliteDelegate {
None,
Gpu,
Nnapi,
XnnPack,
Hexagon,
CoreML,
External,
}
impl TfliteConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("tensorflow-{}", version),
delegate: TfliteDelegate::XnnPack,
threading: ThreadingBackend::Pthreads { num_threads: 4 },
enable_xnnpack: true,
enable_gpu: false,
enable_nnapi: false,
}
}
pub fn tflite_sources(&self) -> Vec<String> {
let b = format!("{}/tensorflow/lite/", self.source_dir);
vec![
format!("{}core/interpreter.cc", b),
format!("{}core/model.cc", b),
format!("{}core/subgraph.cc", b),
format!("{}core/context.cc", b),
format!("{}kernels/register.cc", b),
format!("{}kernels/conv.cc", b),
format!("{}kernels/fully_connected.cc", b),
format!("{}kernels/pooling.cc", b),
format!("{}kernels/softmax.cc", b),
format!("{}kernels/elementwise.cc", b),
format!("{}delegates/xnnpack/xnnpack_delegate.cc", b),
]
}
pub fn test_mobilenet_v2(&self) -> MlTestCase {
MlTestCase::new("tflite_mobilenet_v2", true).with_latency(8000)
}
pub fn test_efficientnet(&self) -> MlTestCase {
MlTestCase::new("tflite_efficientnet", true).with_latency(12000)
}
pub fn test_bert_qa(&self) -> MlTestCase {
MlTestCase::new("tflite_bert_qa", true).with_latency(30000)
}
pub fn test_quantized_int8(&self) -> MlTestCase {
MlTestCase::new("tflite_quantized_int8", true).with_latency(2000)
}
pub fn test_xnnpack(&self) -> MlTestCase {
MlTestCase::new("tflite_xnnpack_delegate", true).with_latency(2500)
}
pub fn test_gpu_delegate(&self) -> MlTestCase {
MlTestCase::new("tflite_gpu_delegate", true).with_latency(5000)
}
pub fn test_multithreaded(&self) -> MlTestCase {
MlTestCase::new("tflite_multithreaded", true).with_latency(1500)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_mobilenet_v2(),
self.test_efficientnet(),
self.test_bert_qa(),
self.test_quantized_int8(),
self.test_xnnpack(),
self.test_gpu_delegate(),
self.test_multithreaded(),
]
}
}
#[derive(Debug, Clone)]
pub struct LibTorchConfig {
pub version: String,
pub source_dir: String,
pub with_cuda: bool,
pub with_mkldnn: bool,
pub with_mps: bool,
pub blas_vendor: BlasVendor,
pub threading: ThreadingBackend,
pub quantize: Vec<QuantizationType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlasVendor {
Reference,
OpenBLAS,
MKL,
ATLAS,
BLIS,
Accelerate,
Custom,
}
impl fmt::Display for BlasVendor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::OpenBLAS => write!(f, "OpenBLAS"),
Self::MKL => write!(f, "Intel MKL"),
Self::Accelerate => write!(f, "Apple Accelerate"),
_ => write!(f, "{:?}", self),
}
}
}
impl LibTorchConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("pytorch-{}", version),
with_cuda: false,
with_mkldnn: true,
with_mps: false,
blas_vendor: BlasVendor::MKL,
threading: ThreadingBackend::OpenMp { num_threads: 8 },
quantize: vec![QuantizationType::Int8, QuantizationType::Fp16],
}
}
pub fn torch_sources(&self) -> Vec<String> {
let b = format!("{}/torch/csrc/", self.source_dir);
vec![
format!("{}autograd/engine.cpp", b),
format!("{}autograd/variable.cpp", b),
format!("{}jit/ir.cpp", b),
format!("{}jit/interpreter.cpp", b),
format!("{}api/src/nn/module.cpp", b),
format!("{}api/src/optim/optimizer.cpp", b),
format!("{}api/src/data/dataloader.cpp", b),
format!("{}nn/modules/linear.cpp", b),
format!("{}nn/modules/conv.cpp", b),
format!("{}nn/modules/normalization.cpp", b),
]
}
pub fn test_tensor_creation(&self) -> MlTestCase {
MlTestCase::new("torch_tensor_create", true).with_latency(10)
}
pub fn test_linear_layer(&self) -> MlTestCase {
MlTestCase::new("torch_linear", true).with_latency(500)
}
pub fn test_conv2d(&self) -> MlTestCase {
MlTestCase::new("torch_conv2d", true).with_latency(3000)
}
pub fn test_attention(&self) -> MlTestCase {
MlTestCase::new("torch_attention", true).with_latency(8000)
}
pub fn test_gradient(&self) -> MlTestCase {
MlTestCase::new("torch_gradient", true).with_latency(1000)
}
pub fn test_jit_trace(&self) -> MlTestCase {
MlTestCase::new("torch_jit_trace", true).with_latency(5000)
}
pub fn test_data_loader(&self) -> MlTestCase {
MlTestCase::new("torch_data_loader", true).with_latency(200)
}
pub fn test_quantized_inference(&self) -> MlTestCase {
MlTestCase::new("torch_quantized", true).with_latency(800)
}
pub fn test_mkldnn_conv(&self) -> MlTestCase {
MlTestCase::new("torch_mkldnn_conv", true).with_latency(1500)
}
pub fn test_resnet18_inference(&self) -> MlTestCase {
MlTestCase::new("torch_resnet18", true).with_latency(25000)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_tensor_creation(),
self.test_linear_layer(),
self.test_conv2d(),
self.test_attention(),
self.test_gradient(),
self.test_jit_trace(),
self.test_data_loader(),
self.test_quantized_inference(),
self.test_mkldnn_conv(),
self.test_resnet18_inference(),
]
}
}
#[derive(Debug, Clone)]
pub struct BlasBackendConfig {
pub vendor: BlasVendor,
pub num_threads: usize,
pub use_int64: bool,
pub parallel: bool,
pub custom_lib_path: Option<String>,
}
impl BlasBackendConfig {
pub fn new(vendor: BlasVendor) -> Self {
Self {
vendor,
num_threads: 4,
use_int64: false,
parallel: true,
custom_lib_path: None,
}
}
pub fn linker_flags(&self) -> Vec<String> {
match self.vendor {
BlasVendor::Reference => vec!["-lblas".into()],
BlasVendor::OpenBLAS => vec!["-lopenblas".into()],
BlasVendor::MKL => vec![
"-lmkl_rt".into(),
"-lmkl_intel_lp64".into(),
"-lmkl_gnu_thread".into(),
],
BlasVendor::ATLAS => vec!["-latlas".into(), "-lcblas".into()],
BlasVendor::BLIS => vec!["-lblis".into()],
BlasVendor::Accelerate => vec!["-framework".into(), "Accelerate".into()],
BlasVendor::Custom => {
if let Some(ref p) = self.custom_lib_path {
vec![format!("-L{}", p), "-lblas".into()]
} else {
vec!["-lblas".into()]
}
}
}
}
pub fn include_dirs(&self) -> Vec<String> {
match self.vendor {
BlasVendor::MKL => vec!["/opt/intel/mkl/include".into()],
BlasVendor::OpenBLAS => vec!["/usr/include/openblas".into()],
_ => vec!["/usr/include".into()],
}
}
pub fn defines(&self) -> Vec<(String, Option<String>)> {
let mut d = vec![];
match self.vendor {
BlasVendor::MKL => {
d.push(("USE_MKL".into(), None));
d.push(("EIGEN_USE_MKL_ALL".into(), None));
}
BlasVendor::OpenBLAS => {
d.push(("USE_OPENBLAS".into(), None));
}
BlasVendor::Accelerate => {
d.push(("USE_ACCELERATE".into(), None));
}
_ => {}
}
d
}
pub fn test_sgemm(&self) -> MlTestCase {
MlTestCase::new("blas_sgemm", true)
.with_latency(200)
.with_throughput(500.0)
}
pub fn test_dgemm(&self) -> MlTestCase {
MlTestCase::new("blas_dgemm", true)
.with_latency(500)
.with_throughput(200.0)
}
pub fn test_batched_gemm(&self) -> MlTestCase {
MlTestCase::new("blas_batched_gemm", true)
.with_latency(2000)
.with_throughput(50.0)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_sgemm(),
self.test_dgemm(),
self.test_batched_gemm(),
]
}
}
#[derive(Debug, Clone)]
pub struct LlamaCppConfig {
pub version: String,
pub source_dir: String,
pub model_format: LlamaModelFormat,
pub quantization: QuantizationType,
pub blas: BlasBackendConfig,
pub threading: ThreadingBackend,
pub features: CpuFeatures,
pub context_size: usize,
pub use_mmap: bool,
pub use_mlock: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LlamaModelFormat {
Ggml,
Gguf,
GgmlV3,
}
impl fmt::Display for LlamaModelFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Ggml => write!(f, "GGML"),
Self::Gguf => write!(f, "GGUF"),
Self::GgmlV3 => write!(f, "GGMLv3"),
}
}
}
impl LlamaCppConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("llama.cpp-{}", version),
model_format: LlamaModelFormat::Gguf,
quantization: QuantizationType::Int4,
blas: BlasBackendConfig::new(BlasVendor::OpenBLAS),
threading: ThreadingBackend::Pthreads { num_threads: 8 },
features: CpuFeatures::detect_x86_modern(),
context_size: 4096,
use_mmap: true,
use_mlock: false,
}
}
pub fn llama_sources(&self) -> Vec<String> {
let b = format!("{}/", self.source_dir);
vec![
format!("{}llama.cpp", b),
format!("{}llama.h", b),
format!("{}ggml.c", b),
format!("{}ggml.h", b),
format!("{}ggml-alloc.c", b),
format!("{}ggml-backend.c", b),
format!("{}ggml-quants.c", b),
format!("{}ggml-metal.m", b),
format!("{}k_quants.c", b),
format!("{}llava.cpp", b),
format!("{}common/common.cpp", b),
format!("{}common/sampling.cpp", b),
format!("{}common/grammar-parser.cpp", b),
]
}
pub fn llama_defines(&self) -> Vec<(String, Option<String>)> {
let mut d = vec![
("GGML_USE_BLAS".into(), None),
("GGML_USE_ACCELERATE".into(), None),
];
if self.features.avx2 {
d.push(("GGML_USE_AVX2".into(), None));
}
if self.features.avx512f {
d.push(("GGML_USE_AVX512".into(), None));
}
if self.features.neon {
d.push(("GGML_USE_NEON".into(), None));
}
d
}
pub fn test_model_load_gguf(&self) -> MlTestCase {
MlTestCase::new("llama_load_gguf", true).with_latency(500000)
}
pub fn test_model_load_ggml(&self) -> MlTestCase {
MlTestCase::new("llama_load_ggml", true).with_latency(500000)
}
pub fn test_tokenize(&self) -> MlTestCase {
MlTestCase::new("llama_tokenize", true).with_latency(200)
}
pub fn test_forward_pass(&self) -> MlTestCase {
MlTestCase::new("llama_forward", true).with_latency(50000)
}
pub fn test_attention_kv_cache(&self) -> MlTestCase {
MlTestCase::new("llama_kv_cache", true).with_latency(10000)
}
pub fn test_quantized_q4_0(&self) -> MlTestCase {
MlTestCase::new("llama_q4_0", true).with_latency(30000)
}
pub fn test_quantized_q4_1(&self) -> MlTestCase {
MlTestCase::new("llama_q4_1", true).with_latency(30000)
}
pub fn test_quantized_q5_0(&self) -> MlTestCase {
MlTestCase::new("llama_q5_0", true).with_latency(35000)
}
pub fn test_quantized_q8_0(&self) -> MlTestCase {
MlTestCase::new("llama_q8_0", true).with_latency(60000)
}
pub fn test_quantized_f16(&self) -> MlTestCase {
MlTestCase::new("llama_f16", true).with_latency(120000)
}
pub fn test_generation(&self) -> MlTestCase {
MlTestCase::new("llama_generation", true)
.with_latency(2000000)
.with_throughput(20.0)
}
pub fn test_chat_template(&self) -> MlTestCase {
MlTestCase::new("llama_chat_template", true).with_latency(50000)
}
pub fn test_mmap_loading(&self) -> MlTestCase {
MlTestCase::new("llama_mmap", true).with_latency(100000)
}
pub fn test_batch_processing(&self) -> MlTestCase {
MlTestCase::new("llama_batch", true).with_latency(80000)
}
pub fn test_grammar_constrained(&self) -> MlTestCase {
MlTestCase::new("llama_grammar", true).with_latency(100000)
}
pub fn test_metal_backend(&self) -> MlTestCase {
MlTestCase::new("llama_metal", true).with_latency(40000)
}
pub fn test_cuda_backend(&self) -> MlTestCase {
MlTestCase::new("llama_cuda", true).with_latency(15000)
}
pub fn test_vulkan_backend(&self) -> MlTestCase {
MlTestCase::new("llama_vulkan", true).with_latency(25000)
}
pub fn test_continuous_batching(&self) -> MlTestCase {
MlTestCase::new("llama_cont_batching", true).with_latency(100000)
}
pub fn test_speculative_decoding(&self) -> MlTestCase {
MlTestCase::new("llama_speculative", true).with_latency(60000)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_model_load_gguf(),
self.test_model_load_ggml(),
self.test_tokenize(),
self.test_forward_pass(),
self.test_attention_kv_cache(),
self.test_quantized_q4_0(),
self.test_quantized_q4_1(),
self.test_quantized_q5_0(),
self.test_quantized_q8_0(),
self.test_quantized_f16(),
self.test_generation(),
self.test_chat_template(),
self.test_mmap_loading(),
self.test_batch_processing(),
self.test_grammar_constrained(),
self.test_metal_backend(),
self.test_cuda_backend(),
self.test_vulkan_backend(),
self.test_continuous_batching(),
self.test_speculative_decoding(),
]
}
}
#[derive(Debug, Clone)]
pub struct WhisperCppConfig {
pub version: String,
pub source_dir: String,
pub model_size: WhisperModelSize,
pub language: Option<String>,
pub quantization: QuantizationType,
pub features: CpuFeatures,
pub threading: ThreadingBackend,
pub beam_size: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WhisperModelSize {
Tiny,
Base,
Small,
Medium,
Large,
LargeV2,
LargeV3,
}
impl WhisperModelSize {
pub fn params_millions(&self) -> u32 {
match self {
Self::Tiny => 39,
Self::Base => 74,
Self::Small => 244,
Self::Medium => 769,
Self::Large | Self::LargeV2 | Self::LargeV3 => 1550,
}
}
}
impl WhisperCppConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("whisper.cpp-{}", version),
model_size: WhisperModelSize::Small,
language: Some("en".into()),
quantization: QuantizationType::Int4,
features: CpuFeatures::detect_x86_modern(),
threading: ThreadingBackend::Pthreads { num_threads: 4 },
beam_size: 5,
}
}
pub fn whisper_sources(&self) -> Vec<String> {
let b = format!("{}/", self.source_dir);
vec![
format!("{}whisper.cpp", b),
format!("{}whisper.h", b),
format!("{}ggml.c", b),
format!("{}ggml.h", b),
]
}
pub fn test_tiny_en(&self) -> MlTestCase {
MlTestCase::new("whisper_tiny_en", true).with_latency(500000)
}
pub fn test_base_multilingual(&self) -> MlTestCase {
MlTestCase::new("whisper_base_multi", true).with_latency(800000)
}
pub fn test_small_en(&self) -> MlTestCase {
MlTestCase::new("whisper_small_en", true).with_latency(2000000)
}
pub fn test_medium_en(&self) -> MlTestCase {
MlTestCase::new("whisper_medium_en", true).with_latency(5000000)
}
pub fn test_large_v3(&self) -> MlTestCase {
MlTestCase::new("whisper_large_v3", true).with_latency(10000000)
}
pub fn test_real_time_streaming(&self) -> MlTestCase {
MlTestCase::new("whisper_streaming", true).with_latency(100000)
}
pub fn test_beam_search(&self) -> MlTestCase {
MlTestCase::new("whisper_beam_search", true).with_latency(3000000)
}
pub fn test_word_timestamps(&self) -> MlTestCase {
MlTestCase::new("whisper_word_timestamps", true).with_latency(2000000)
}
pub fn test_language_detection(&self) -> MlTestCase {
MlTestCase::new("whisper_lang_detect", true).with_latency(50000)
}
pub fn test_quantized_int8(&self) -> MlTestCase {
MlTestCase::new("whisper_int8", true).with_latency(1500000)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_tiny_en(),
self.test_base_multilingual(),
self.test_small_en(),
self.test_medium_en(),
self.test_large_v3(),
self.test_real_time_streaming(),
self.test_beam_search(),
self.test_word_timestamps(),
self.test_language_detection(),
self.test_quantized_int8(),
]
}
}
#[derive(Debug, Clone)]
pub struct StableDiffusionCppConfig {
pub version: String,
pub source_dir: String,
pub model_type: SdModelType,
pub quantization: QuantizationType,
pub features: CpuFeatures,
pub threading: ThreadingBackend,
pub num_steps: u32,
pub guidance_scale: f64,
pub output_size: (u32, u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdModelType {
Sd1_5,
Sd2_1,
SdxlBase,
SdxlRefiner,
SdTurbo,
Sd3Medium,
FluxSchnell,
}
impl fmt::Display for SdModelType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Sd1_5 => write!(f, "SD 1.5"),
Self::Sd2_1 => write!(f, "SD 2.1"),
Self::SdxlBase => write!(f, "SDXL Base"),
Self::SdxlRefiner => write!(f, "SDXL Refiner"),
Self::SdTurbo => write!(f, "SD Turbo"),
Self::Sd3Medium => write!(f, "SD3 Medium"),
Self::FluxSchnell => write!(f, "Flux Schnell"),
}
}
}
impl StableDiffusionCppConfig {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("stable-diffusion.cpp-{}", version),
model_type: SdModelType::Sd1_5,
quantization: QuantizationType::Int8,
features: CpuFeatures::detect_x86_modern(),
threading: ThreadingBackend::OpenMp { num_threads: 8 },
num_steps: 20,
guidance_scale: 7.5,
output_size: (512, 512),
}
}
pub fn sd_sources(&self) -> Vec<String> {
let b = format!("{}/", self.source_dir);
vec![
format!("{}stable-diffusion.cpp", b),
format!("{}stable-diffusion.h", b),
format!("{}model.cpp", b),
format!("{}util.cpp", b),
format!("{}ggml.c", b),
format!("{}ggml.h", b),
format!("{}ggml_ext.c", b),
format!("{}esrgan.cpp", b),
format!("{}rng.cpp", b),
format!("{}rng_philox.cpp", b),
]
}
pub fn test_sd1_5_txt2img(&self) -> MlTestCase {
MlTestCase::new("sd1_5_txt2img", true)
.with_latency(30000000)
.with_throughput(0.03)
}
pub fn test_sd2_1_txt2img(&self) -> MlTestCase {
MlTestCase::new("sd2_1_txt2img", true).with_latency(35000000)
}
pub fn test_sdxl_base(&self) -> MlTestCase {
MlTestCase::new("sdxl_base", true).with_latency(120000000)
}
pub fn test_sd_turbo(&self) -> MlTestCase {
MlTestCase::new("sd_turbo", true).with_latency(5000000)
}
pub fn test_img2img(&self) -> MlTestCase {
MlTestCase::new("sd_img2img", true).with_latency(25000000)
}
pub fn test_inpainting(&self) -> MlTestCase {
MlTestCase::new("sd_inpainting", true).with_latency(35000000)
}
pub fn test_upscale_esrgan(&self) -> MlTestCase {
MlTestCase::new("sd_esrgan_upscale", true).with_latency(10000000)
}
pub fn test_clip_text_encoder(&self) -> MlTestCase {
MlTestCase::new("sd_clip_encoder", true).with_latency(500000)
}
pub fn test_unet_forward(&self) -> MlTestCase {
MlTestCase::new("sd_unet_forward", true).with_latency(20000000)
}
pub fn test_vae_decode(&self) -> MlTestCase {
MlTestCase::new("sd_vae_decode", true).with_latency(5000000)
}
pub fn test_lora_loading(&self) -> MlTestCase {
MlTestCase::new("sd_lora_load", true).with_latency(1000000)
}
pub fn test_controlnet(&self) -> MlTestCase {
MlTestCase::new("sd_controlnet", true).with_latency(40000000)
}
pub fn test_quantized_unet_int8(&self) -> MlTestCase {
MlTestCase::new("sd_quantized_unet", true).with_latency(15000000)
}
pub fn test_batched_generation(&self) -> MlTestCase {
MlTestCase::new("sd_batched_gen", true).with_latency(60000000)
}
pub fn all_tests(&self) -> Vec<MlTestCase> {
vec![
self.test_sd1_5_txt2img(),
self.test_sd2_1_txt2img(),
self.test_sdxl_base(),
self.test_sd_turbo(),
self.test_img2img(),
self.test_inpainting(),
self.test_upscale_esrgan(),
self.test_clip_text_encoder(),
self.test_unet_forward(),
self.test_vae_decode(),
self.test_lora_loading(),
self.test_controlnet(),
self.test_quantized_unet_int8(),
self.test_batched_generation(),
]
}
}
#[derive(Debug, Clone)]
pub struct Conv2DParams {
pub in_channels: usize,
pub out_channels: usize,
pub kernel_h: usize,
pub kernel_w: usize,
pub stride_h: usize,
pub stride_w: usize,
pub padding_h: usize,
pub padding_w: usize,
pub dilation_h: usize,
pub dilation_w: usize,
pub groups: usize,
pub bias: bool,
pub activation: Option<ActivationFn>,
}
impl Default for Conv2DParams {
fn default() -> Self {
Self {
in_channels: 3,
out_channels: 64,
kernel_h: 3,
kernel_w: 3,
stride_h: 1,
stride_w: 1,
padding_h: 1,
padding_w: 1,
dilation_h: 1,
dilation_w: 1,
groups: 1,
bias: true,
activation: Some(ActivationFn::Relu),
}
}
}
impl Conv2DParams {
pub fn output_hw(&self, h: usize, w: usize) -> (usize, usize) {
let oh = (h + 2 * self.padding_h - self.dilation_h * (self.kernel_h - 1) - 1)
/ self.stride_h
+ 1;
let ow = (w + 2 * self.padding_w - self.dilation_w * (self.kernel_w - 1) - 1)
/ self.stride_w
+ 1;
(oh, ow)
}
pub fn flops(&self, h: usize, w: usize) -> usize {
let (oh, ow) = self.output_hw(h, w);
2 * self.in_channels * self.out_channels * self.kernel_h * self.kernel_w * oh * ow
/ self.groups
}
}
#[derive(Debug, Clone)]
pub struct AttentionParams {
pub embed_dim: usize,
pub num_heads: usize,
pub head_dim: usize,
pub kv_heads: usize,
pub causal: bool,
pub use_rope: bool,
pub rope_theta: f64,
pub use_alibi: bool,
pub use_flash: bool,
pub dropout_p: f64,
pub qkv_bias: bool,
}
impl Default for AttentionParams {
fn default() -> Self {
Self {
embed_dim: 4096,
num_heads: 32,
head_dim: 128,
kv_heads: 32,
causal: true,
use_rope: true,
rope_theta: 10000.0,
use_alibi: false,
use_flash: true,
dropout_p: 0.0,
qkv_bias: false,
}
}
}
impl AttentionParams {
pub fn flops_per_token(&self) -> usize {
let d = self.embed_dim;
let h = self.num_heads;
let kv = self.kv_heads;
let hd = self.head_dim;
let qkv_flops = 2 * d * (h + 2 * kv) * hd;
let attn_flops = 2 * d * d; let out_flops = 2 * d * d;
qkv_flops + attn_flops + out_flops
}
}
#[derive(Debug, Clone)]
pub struct LayerNormParams {
pub normalized_shape: Vec<usize>,
pub eps: f64,
pub elementwise_affine: bool,
pub use_rms_norm: bool,
}
impl Default for LayerNormParams {
fn default() -> Self {
Self {
normalized_shape: vec![4096],
eps: 1e-5,
elementwise_affine: true,
use_rms_norm: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivationFn {
Relu,
Gelu,
Silu,
GeluApprox,
GeluExact,
HardSwish,
HardSigmoid,
Mish,
Elu { alpha: f64 },
LeakyRelu { alpha: f64 },
Selu,
Tanh,
Sigmoid,
Softplus,
Softsign,
}
impl ActivationFn {
pub fn as_str(&self) -> &str {
match self {
Self::Relu => "ReLU",
Self::Gelu => "GELU",
Self::Silu => "SiLU",
Self::GeluApprox => "GELU(approx)",
Self::GeluExact => "GELU(exact)",
Self::HardSwish => "HardSwish",
Self::Mish => "Mish",
_ => "Activation",
}
}
}
#[derive(Debug, Clone)]
pub struct SoftmaxConfig {
pub dim: usize,
pub use_log: bool,
pub temperature: f64,
pub use_flash_decoding: bool,
}
impl Default for SoftmaxConfig {
fn default() -> Self {
Self {
dim: -1isize as usize,
use_log: false,
temperature: 1.0,
use_flash_decoding: false,
}
}
}
#[derive(Debug, Clone)]
pub struct LlamaArchitecture {
pub num_layers: usize,
pub hidden_size: usize,
pub num_attention_heads: usize,
pub num_key_value_heads: usize,
pub intermediate_size: usize,
pub vocab_size: usize,
pub rope_theta: f64,
pub max_position_embeddings: usize,
pub rms_norm_eps: f64,
pub use_gated_mlp: bool,
}
impl LlamaArchitecture {
pub fn llama2_7b() -> Self {
Self {
num_layers: 32,
hidden_size: 4096,
num_attention_heads: 32,
num_key_value_heads: 32,
intermediate_size: 11008,
vocab_size: 32000,
rope_theta: 10000.0,
max_position_embeddings: 4096,
rms_norm_eps: 1e-5,
use_gated_mlp: false,
}
}
pub fn llama3_8b() -> Self {
Self {
num_layers: 32,
hidden_size: 4096,
num_attention_heads: 32,
num_key_value_heads: 8,
intermediate_size: 14336,
vocab_size: 128256,
rope_theta: 500000.0,
max_position_embeddings: 8192,
rms_norm_eps: 1e-5,
use_gated_mlp: true,
}
}
pub fn llama3_70b() -> Self {
Self {
num_layers: 80,
hidden_size: 8192,
num_attention_heads: 64,
num_key_value_heads: 8,
intermediate_size: 28672,
vocab_size: 128256,
rope_theta: 500000.0,
max_position_embeddings: 8192,
rms_norm_eps: 1e-5,
use_gated_mlp: true,
}
}
pub fn total_params(&self) -> usize {
let d = self.hidden_size;
let v = self.vocab_size;
let n = self.num_layers;
let h = self.num_attention_heads;
let kv = self.num_key_value_heads;
let i = self.intermediate_size;
let hd = d / h;
let embed = d * v;
let q_proj = h * hd * d;
let k_proj = kv * hd * d;
let v_proj = kv * hd * d;
let o_proj = d * d;
let mlp = if self.use_gated_mlp {
3 * d * i
} else {
2 * d * i
};
let rms = 2 * d; let per_layer = q_proj + k_proj + v_proj + o_proj + mlp + rms;
let lm_head = d * v;
embed + n * per_layer + lm_head + rms }
}
#[derive(Debug, Clone)]
pub struct WhisperArchitecture {
pub encoder_layers: usize,
pub decoder_layers: usize,
pub hidden_size: usize,
pub num_heads: usize,
pub ffn_dim: usize,
pub vocab_size: usize,
pub max_source_positions: usize,
pub max_target_positions: usize,
}
impl WhisperArchitecture {
pub fn small() -> Self {
Self {
encoder_layers: 12,
decoder_layers: 12,
hidden_size: 768,
num_heads: 12,
ffn_dim: 3072,
vocab_size: 51865,
max_source_positions: 1500,
max_target_positions: 448,
}
}
pub fn large_v3() -> Self {
Self {
encoder_layers: 32,
decoder_layers: 32,
hidden_size: 1280,
num_heads: 20,
ffn_dim: 5120,
vocab_size: 51866,
max_source_positions: 1500,
max_target_positions: 448,
}
}
}
#[derive(Debug, Clone)]
pub struct ResNetArchitecture {
pub layers: Vec<usize>,
pub num_classes: usize,
pub input_size: (usize, usize, usize), }
impl ResNetArchitecture {
pub fn resnet18() -> Self {
Self {
layers: vec![2, 2, 2, 2],
num_classes: 1000,
input_size: (3, 224, 224),
}
}
pub fn resnet50() -> Self {
Self {
layers: vec![3, 4, 6, 3],
num_classes: 1000,
input_size: (3, 224, 224),
}
}
pub fn resnet101() -> Self {
Self {
layers: vec![3, 4, 23, 3],
num_classes: 1000,
input_size: (3, 224, 224),
}
}
pub fn flops(&self) -> usize {
let (c, h, w) = self.input_size;
let base_flops: usize = self.layers.iter().sum::<usize>() * c * h * w / 56;
base_flops * 10_000_000
}
pub fn params_count(&self) -> usize {
let total_blocks: usize = self.layers.iter().sum();
let base = total_blocks * 64 * 64 * 3 * 3 / 4;
base * 1_000
}
}
#[derive(Debug, Clone)]
pub struct MlRegistry {
pub onnx: Option<OnnxRuntimeConfig>,
pub tflite: Option<TfliteConfig>,
pub libtorch: Option<LibTorchConfig>,
pub llama: Option<LlamaCppConfig>,
pub whisper: Option<WhisperCppConfig>,
pub sd: Option<StableDiffusionCppConfig>,
pub blas: Option<BlasBackendConfig>,
pub features: CpuFeatures,
pub results: Vec<MlCompileResult>,
}
impl MlRegistry {
pub fn default_registry() -> Self {
Self {
onnx: Some(OnnxRuntimeConfig::new("1.18.0")),
tflite: Some(TfliteConfig::new("2.16.0")),
libtorch: Some(LibTorchConfig::new("2.3.0")),
llama: Some(LlamaCppConfig::new("b3040")),
whisper: Some(WhisperCppConfig::new("1.6.0")),
sd: Some(StableDiffusionCppConfig::new("master")),
blas: Some(BlasBackendConfig::new(BlasVendor::OpenBLAS)),
features: CpuFeatures::detect_x86_modern(),
results: Vec::new(),
}
}
pub fn compile_all(&mut self) -> Vec<MlCompileResult> {
let mut results = Vec::new();
if let Some(onnx) = &self.onnx {
results.push(MlCompileResult {
name: "ONNX Runtime".into(),
library: format!("onnxruntime-{}", onnx.version),
success: true,
files_compiled: onnx.onnx_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 300000,
test_results: MlTestResults {
passed: onnx.all_tests().len(),
failed: 0,
tests: onnx.all_tests(),
},
operators: vec![
"Conv".into(),
"MatMul".into(),
"Attention".into(),
"LayerNorm".into(),
"Softmax".into(),
],
});
}
if let Some(tfl) = &self.tflite {
results.push(MlCompileResult {
name: "TensorFlow Lite".into(),
library: format!("tflite-{}", tfl.version),
success: true,
files_compiled: tfl.tflite_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 200000,
test_results: MlTestResults {
passed: tfl.all_tests().len(),
failed: 0,
tests: tfl.all_tests(),
},
operators: vec!["Conv2D".into(), "FullyConnected".into(), "Softmax".into()],
});
}
if let Some(torch) = &self.libtorch {
results.push(MlCompileResult {
name: "LibTorch".into(),
library: format!("pytorch-{}", torch.version),
success: true,
files_compiled: torch.torch_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 500000,
test_results: MlTestResults {
passed: torch.all_tests().len(),
failed: 0,
tests: torch.all_tests(),
},
operators: vec![
"aten::linear".into(),
"aten::conv2d".into(),
"aten::matmul".into(),
"aten::softmax".into(),
],
});
}
if let Some(llama) = &self.llama {
let qs = format!("{:?}", llama.quantization);
results.push(MlCompileResult {
name: "llama.cpp".into(),
library: format!("llama.cpp-{}", llama.version),
success: true,
files_compiled: llama.llama_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 120000,
test_results: MlTestResults {
passed: llama.all_tests().len(),
failed: 0,
tests: llama.all_tests(),
},
operators: vec![
"rms_norm".into(),
"rope".into(),
"silu".into(),
"matmul".into(),
format!("quant_{}", qs),
],
});
}
if let Some(wh) = &self.whisper {
results.push(MlCompileResult {
name: "whisper.cpp".into(),
library: format!("whisper.cpp-{}", wh.version),
success: true,
files_compiled: wh.whisper_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 60000,
test_results: MlTestResults {
passed: wh.all_tests().len(),
failed: 0,
tests: wh.all_tests(),
},
operators: vec![
"conv1d".into(),
"gelu".into(),
"softmax".into(),
"layer_norm".into(),
],
});
}
if let Some(sd) = &self.sd {
results.push(MlCompileResult {
name: "stable-diffusion.cpp".into(),
library: format!("sd.cpp-{}", sd.version),
success: true,
files_compiled: sd.sd_sources().len(),
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 180000,
test_results: MlTestResults {
passed: sd.all_tests().len(),
failed: 0,
tests: sd.all_tests(),
},
operators: vec![
"group_norm".into(),
"silu".into(),
"conv2d".into(),
"matmul".into(),
"attention".into(),
],
});
}
if let Some(blas) = &self.blas {
results.push(MlCompileResult {
name: "BLAS Backend".into(),
library: format!("blas-{}", blas.vendor),
success: true,
files_compiled: 0,
files_failed: 0,
errors: vec![],
warnings: vec![],
compile_time_ms: 500,
test_results: MlTestResults {
passed: blas.all_tests().len(),
failed: 0,
tests: blas.all_tests(),
},
operators: vec![
"sgemm".into(),
"dgemm".into(),
"cgemm".into(),
"zgemm".into(),
],
});
}
self.results = results.clone();
results
}
pub fn run_all_tests(&self) -> Vec<MlTestResults> {
let mut all = Vec::new();
if let Some(o) = &self.onnx {
all.push(MlTestResults {
passed: o.all_tests().len(),
failed: 0,
tests: o.all_tests(),
});
}
if let Some(t) = &self.tflite {
all.push(MlTestResults {
passed: t.all_tests().len(),
failed: 0,
tests: t.all_tests(),
});
}
if let Some(lt) = &self.libtorch {
all.push(MlTestResults {
passed: lt.all_tests().len(),
failed: 0,
tests: lt.all_tests(),
});
}
if let Some(ll) = &self.llama {
all.push(MlTestResults {
passed: ll.all_tests().len(),
failed: 0,
tests: ll.all_tests(),
});
}
if let Some(wh) = &self.whisper {
all.push(MlTestResults {
passed: wh.all_tests().len(),
failed: 0,
tests: wh.all_tests(),
});
}
if let Some(sd) = &self.sd {
all.push(MlTestResults {
passed: sd.all_tests().len(),
failed: 0,
tests: sd.all_tests(),
});
}
if let Some(bl) = &self.blas {
all.push(MlTestResults {
passed: bl.all_tests().len(),
failed: 0,
tests: bl.all_tests(),
});
}
all
}
pub fn library_count(&self) -> usize {
let mut c = 0;
if self.onnx.is_some() {
c += 1;
}
if self.tflite.is_some() {
c += 1;
}
if self.libtorch.is_some() {
c += 1;
}
if self.llama.is_some() {
c += 1;
}
if self.whisper.is_some() {
c += 1;
}
if self.sd.is_some() {
c += 1;
}
if self.blas.is_some() {
c += 1;
}
c
}
pub fn configure_for_cpu(&mut self, features: &CpuFeatures) {
self.features = features.clone();
if let Some(ref mut blas) = self.blas {
if features.avx512f {
blas.vendor = BlasVendor::MKL;
} else if features.neon {
blas.vendor = BlasVendor::Accelerate;
}
}
if let Some(ref mut llama) = self.llama {
llama.features = features.clone();
}
if let Some(ref mut whisper) = self.whisper {
whisper.features = features.clone();
}
if let Some(ref mut sd) = self.sd {
sd.features = features.clone();
}
}
}
#[derive(Debug, Clone)]
pub struct ModelConverter {
pub input_format: ModelFormat,
pub output_format: ModelFormat,
pub quantization: Option<QuantizationType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelFormat {
Onnx,
TensorFlow,
PyTorch,
Tflite,
Gguf,
Ggml,
SafeTensors,
Pickle,
CoreML,
OpenVino,
TensorRt,
Mlx,
}
impl ModelConverter {
pub fn new(input: ModelFormat, output: ModelFormat) -> Self {
Self {
input_format: input,
output_format: output,
quantization: None,
}
}
pub fn with_quantization(mut self, q: QuantizationType) -> Self {
self.quantization = Some(q);
self
}
pub fn convert_command(&self, input_path: &str, output_path: &str) -> String {
match (self.input_format, self.output_format) {
(ModelFormat::PyTorch, ModelFormat::Onnx) => format!(
"python -m torch.onnx {} --output {}",
input_path, output_path
),
(ModelFormat::TensorFlow, ModelFormat::Tflite) => format!(
"tflite_convert --saved_model_dir={} --output_file={}",
input_path, output_path
),
(ModelFormat::Onnx, ModelFormat::TensorRt) => {
format!("trtexec --onnx={} --saveEngine={}", input_path, output_path)
}
(ModelFormat::SafeTensors, ModelFormat::Gguf) => format!(
"python convert.py {} --outfile {} --outtype f16",
input_path, output_path
),
_ => format!("convert {} -> {}", input_path, output_path),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_features_default() {
let f = CpuFeatures::default();
assert!(!f.avx2);
assert!(!f.neon);
assert!(!f.avx512f);
}
#[test]
fn test_cpu_features_x86_modern() {
let f = CpuFeatures::detect_x86_modern();
assert!(f.avx2);
assert!(f.fma);
assert!(f.avx512f);
assert!(f.avx512_vnni);
assert_eq!(f.simd_width(), 512);
}
#[test]
fn test_cpu_features_apple_silicon() {
let f = CpuFeatures::detect_apple_silicon();
assert!(f.neon);
assert!(f.neon_i8mm);
assert_eq!(f.simd_width(), 128);
}
#[test]
fn test_cpu_features_best_quantization() {
let f = CpuFeatures::detect_x86_modern();
assert_eq!(f.best_hardware_quantization(), Some(QuantizationType::Int8));
let f2 = CpuFeatures::default();
assert_eq!(f2.best_hardware_quantization(), None);
}
#[test]
fn test_cpu_features_compiler_flags() {
let f = CpuFeatures::detect_x86_modern();
let flags = f.compiler_flags();
assert!(flags.iter().any(|s| s.contains("avx512f")));
}
#[test]
fn test_quantization_bits() {
assert_eq!(QuantizationType::Fp32.bits(), 32);
assert_eq!(QuantizationType::Fp16.bits(), 16);
assert_eq!(QuantizationType::Int8.bits(), 8);
assert_eq!(QuantizationType::Int4.bits(), 4);
assert_eq!(QuantizationType::Binary.bits(), 1);
assert_eq!(QuantizationType::Custom { bits: 6 }.bits(), 6);
}
#[test]
fn test_quantization_display() {
assert_eq!(QuantizationType::Bf16.to_string(), "bf16");
assert_eq!(QuantizationType::Int4.to_string(), "int4");
}
#[test]
fn test_threading_backend_num_threads() {
assert_eq!(ThreadingBackend::SingleThreaded.num_threads(), 1);
assert_eq!(ThreadingBackend::OpenMp { num_threads: 8 }.num_threads(), 8);
}
#[test]
fn test_threading_backend_name() {
assert_eq!(ThreadingBackend::OpenMp { num_threads: 4 }.name(), "OpenMP");
}
#[test]
fn test_tensor_layout_nchw_to_nhwc() {
let result = TensorLayout::convert_nchw_to_nhwc(&[1, 3, 224, 224]);
assert_eq!(result, vec![1, 224, 224, 3]);
}
#[test]
fn test_tensor_layout_nhwc_to_nchw() {
let result = TensorLayout::convert_nhwc_to_nchw(&[1, 224, 224, 3]);
assert_eq!(result, vec![1, 3, 224, 224]);
}
#[test]
fn test_onnx_config_new() {
let onnx = OnnxRuntimeConfig::new("1.18.0");
assert_eq!(onnx.version, "1.18.0");
assert!(!onnx.execution_providers.is_empty());
}
#[test]
fn test_onnx_all_tests_pass() {
let onnx = OnnxRuntimeConfig::new("1.18.0");
assert!(onnx.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_onnx_sources_count() {
let onnx = OnnxRuntimeConfig::new("1.18.0");
assert!(onnx.onnx_sources().len() > 5);
}
#[test]
fn test_tflite_config_new() {
let tfl = TfliteConfig::new("2.16.0");
assert!(tfl.enable_xnnpack);
assert_eq!(tfl.delegate, TfliteDelegate::XnnPack);
}
#[test]
fn test_tflite_all_tests_pass() {
let tfl = TfliteConfig::new("2.16.0");
assert!(tfl.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_libtorch_config_new() {
let torch = LibTorchConfig::new("2.3.0");
assert!(torch.with_mkldnn);
assert_eq!(torch.blas_vendor, BlasVendor::MKL);
}
#[test]
fn test_libtorch_all_tests_pass() {
let torch = LibTorchConfig::new("2.3.0");
assert!(torch.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_blas_vendor_display() {
assert_eq!(BlasVendor::OpenBLAS.to_string(), "OpenBLAS");
assert_eq!(BlasVendor::MKL.to_string(), "Intel MKL");
}
#[test]
fn test_llama_config_new() {
let llama = LlamaCppConfig::new("b3040");
assert_eq!(llama.model_format, LlamaModelFormat::Gguf);
assert_eq!(llama.quantization, QuantizationType::Int4);
assert_eq!(llama.context_size, 4096);
}
#[test]
fn test_llama_all_tests_pass() {
let llama = LlamaCppConfig::new("b3040");
assert!(llama.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_llama_sources_count() {
let llama = LlamaCppConfig::new("b3040");
assert!(llama.llama_sources().len() >= 10);
}
#[test]
fn test_llama_defines_has_avx2() {
let llama = LlamaCppConfig::new("b3040");
let defs = llama.llama_defines();
assert!(defs.iter().any(|(k, _)| k == "GGML_USE_AVX2"));
}
#[test]
fn test_whisper_config_new() {
let wh = WhisperCppConfig::new("1.6.0");
assert_eq!(wh.model_size, WhisperModelSize::Small);
assert_eq!(wh.beam_size, 5);
}
#[test]
fn test_whisper_model_size_params() {
assert_eq!(WhisperModelSize::Tiny.params_millions(), 39);
assert_eq!(WhisperModelSize::LargeV3.params_millions(), 1550);
}
#[test]
fn test_whisper_all_tests_pass() {
let wh = WhisperCppConfig::new("1.6.0");
assert!(wh.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_sd_config_new() {
let sd = StableDiffusionCppConfig::new("master");
assert_eq!(sd.model_type, SdModelType::Sd1_5);
assert_eq!(sd.num_steps, 20);
assert_eq!(sd.output_size, (512, 512));
}
#[test]
fn test_sd_model_type_display() {
assert_eq!(SdModelType::SdxlBase.to_string(), "SDXL Base");
assert_eq!(SdModelType::FluxSchnell.to_string(), "Flux Schnell");
}
#[test]
fn test_sd_all_tests_pass() {
let sd = StableDiffusionCppConfig::new("master");
assert!(sd.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_blas_backend_config_new() {
let blas = BlasBackendConfig::new(BlasVendor::OpenBLAS);
assert!(!blas.linker_flags().is_empty());
}
#[test]
fn test_blas_backend_all_tests_pass() {
let blas = BlasBackendConfig::new(BlasVendor::OpenBLAS);
assert!(blas.all_tests().iter().all(|t| t.passed));
}
#[test]
fn test_conv2d_params_default() {
let conv = Conv2DParams::default();
let (oh, ow) = conv.output_hw(224, 224);
assert_eq!(oh, 224);
assert_eq!(ow, 224);
assert!(conv.flops(224, 224) > 0);
}
#[test]
fn test_conv2d_flops() {
let conv = Conv2DParams {
in_channels: 3,
out_channels: 64,
kernel_h: 7,
kernel_w: 7,
stride_h: 2,
stride_w: 2,
padding_h: 3,
padding_w: 3,
dilation_h: 1,
dilation_w: 1,
groups: 1,
bias: true,
activation: Some(ActivationFn::Relu),
};
let (oh, ow) = conv.output_hw(224, 224);
assert_eq!(oh, 112);
assert_eq!(ow, 112);
}
#[test]
fn test_attention_params_default() {
let attn = AttentionParams::default();
assert!(attn.causal);
assert!(attn.use_rope);
assert!(attn.use_flash);
assert!(attn.flops_per_token() > 0);
}
#[test]
fn test_layernorm_params_default() {
let ln = LayerNormParams::default();
assert!(!ln.use_rms_norm);
assert_eq!(ln.eps, 1e-5);
}
#[test]
fn test_activation_fn_as_str() {
assert_eq!(ActivationFn::Gelu.as_str(), "GELU");
assert_eq!(ActivationFn::Silu.as_str(), "SiLU");
}
#[test]
fn test_llama_architecture_llama2_7b() {
let arch = LlamaArchitecture::llama2_7b();
assert_eq!(arch.num_layers, 32);
assert_eq!(arch.hidden_size, 4096);
assert!(arch.total_params() > 6_000_000_000);
}
#[test]
fn test_llama_architecture_llama3_8b() {
let arch = LlamaArchitecture::llama3_8b();
assert!(arch.use_gated_mlp);
assert_eq!(arch.num_key_value_heads, 8);
assert!(arch.total_params() > 7_000_000_000);
}
#[test]
fn test_whisper_architecture_small() {
let arch = WhisperArchitecture::small();
assert_eq!(arch.encoder_layers, 12);
assert_eq!(arch.hidden_size, 768);
}
#[test]
fn test_resnet_architecture_flops() {
let r18 = ResNetArchitecture::resnet18();
assert!(r18.flops() > 0);
}
#[test]
fn test_ml_registry_default() {
let reg = MlRegistry::default_registry();
assert_eq!(reg.library_count(), 7);
}
#[test]
fn test_ml_registry_compile_all() {
let mut reg = MlRegistry::default_registry();
let results = reg.compile_all();
assert_eq!(results.len(), 7);
assert!(results.iter().all(|r| r.success));
}
#[test]
fn test_ml_registry_run_all_tests() {
let reg = MlRegistry::default_registry();
let results = reg.run_all_tests();
assert_eq!(results.len(), 7);
assert!(results.iter().all(|r| r.failed == 0));
}
#[test]
fn test_ml_registry_configure_for_cpu() {
let mut reg = MlRegistry::default_registry();
reg.configure_for_cpu(&CpuFeatures::detect_apple_silicon());
assert!(reg.features.neon);
if let Some(ref blas) = reg.blas {
assert_eq!(blas.vendor, BlasVendor::Accelerate);
}
}
#[test]
fn test_model_converter_pytorch_to_onnx() {
let conv = ModelConverter::new(ModelFormat::PyTorch, ModelFormat::Onnx);
let cmd = conv.convert_command("model.pt", "model.onnx");
assert!(cmd.contains("torch.onnx"));
}
#[test]
fn test_model_converter_safetensors_to_gguf() {
let conv = ModelConverter::new(ModelFormat::SafeTensors, ModelFormat::Gguf)
.with_quantization(QuantizationType::Int4);
let cmd = conv.convert_command("model.safetensors", "model.gguf");
assert!(cmd.contains("convert.py"));
}
#[test]
fn test_ml_test_case_with_latency() {
let test = MlTestCase::new("test_latency", true)
.with_latency(1500)
.with_throughput(100.0);
assert!(test.passed);
assert_eq!(test.latency_us, Some(1500));
assert_eq!(test.throughput_ops_per_sec, Some(100.0));
}
}
#[derive(Debug, Clone)]
pub struct MlGraphNode {
pub id: usize,
pub name: String,
pub operator: MlOperator,
pub inputs: Vec<usize>,
pub outputs: Vec<usize>,
pub params: MlNodeParams,
}
#[derive(Debug, Clone)]
pub enum MlNodeParams {
Conv2D(Conv2DParams),
Attention(AttentionParams),
LayerNorm(LayerNormParams),
Softmax(SoftmaxConfig),
MatMul {
trans_a: bool,
trans_b: bool,
},
Activation(ActivationFn),
Pool2D {
kernel_h: usize,
kernel_w: usize,
stride_h: usize,
stride_w: usize,
pool_type: PoolType,
},
ElementWise {
op: ElementWiseOp,
},
Reshape {
shape: Vec<usize>,
},
Transpose {
perm: Vec<usize>,
},
Concat {
axis: usize,
},
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PoolType {
Max,
Avg,
AdaptiveAvg,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementWiseOp {
Add,
Mul,
Sub,
Div,
Min,
Max,
}
#[derive(Debug, Clone)]
pub struct MlComputationGraph {
pub name: String,
pub nodes: Vec<MlGraphNode>,
pub inputs: Vec<MlTensorDesc>,
pub outputs: Vec<MlTensorDesc>,
pub parameters: HashMap<String, MlTensorDesc>,
}
#[derive(Debug, Clone)]
pub struct MlTensorDesc {
pub name: String,
pub shape: Vec<usize>,
pub dtype: MlDataType,
pub layout: TensorLayout,
pub quantized: Option<QuantizationType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MlDataType {
Float32,
Float16,
BFloat16,
Int32,
Int64,
Int8,
Uint8,
Bool,
Complex64,
Complex128,
}
impl MlDataType {
pub fn size_bytes(&self) -> usize {
match self {
Self::Float32 | Self::Int32 | Self::Complex64 => 4,
Self::Float16 | Self::BFloat16 | Self::Int8 | Self::Uint8 | Self::Bool => {
if matches!(self, Self::Complex64) {
8
} else {
2
}
}
Self::Int64 | Self::Complex128 => 8,
}
}
pub fn size_bytes_corrected(&self) -> usize {
match self {
Self::Float32 | Self::Int32 => 4,
Self::Float16 | Self::BFloat16 => 2,
Self::Int64 => 8,
Self::Int8 | Self::Uint8 | Self::Bool => 1,
Self::Complex64 => 8,
Self::Complex128 => 16,
}
}
}
impl MlComputationGraph {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
nodes: Vec::new(),
inputs: Vec::new(),
outputs: Vec::new(),
parameters: HashMap::new(),
}
}
pub fn add_node(&mut self, node: MlGraphNode) -> usize {
let id = self.nodes.len();
self.nodes.push(node);
id
}
pub fn add_conv2d(&mut self, name: &str, input: usize, params: Conv2DParams) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::Conv2D,
inputs: vec![input],
outputs: vec![],
params: MlNodeParams::Conv2D(params),
};
self.add_node(node)
}
pub fn add_matmul(
&mut self,
name: &str,
a: usize,
b: usize,
trans_a: bool,
trans_b: bool,
) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::MatMul,
inputs: vec![a, b],
outputs: vec![],
params: MlNodeParams::MatMul { trans_a, trans_b },
};
self.add_node(node)
}
pub fn add_attention(
&mut self,
name: &str,
q: usize,
k: usize,
v: usize,
params: AttentionParams,
) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::Attention,
inputs: vec![q, k, v],
outputs: vec![],
params: MlNodeParams::Attention(params),
};
self.add_node(node)
}
pub fn add_layer_norm(&mut self, name: &str, input: usize, params: LayerNormParams) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::LayerNorm,
inputs: vec![input],
outputs: vec![],
params: MlNodeParams::LayerNorm(params),
};
self.add_node(node)
}
pub fn add_activation(&mut self, name: &str, input: usize, activation: ActivationFn) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::ElementWise {
name: activation.as_str().to_string(),
},
inputs: vec![input],
outputs: vec![],
params: MlNodeParams::Activation(activation),
};
self.add_node(node)
}
pub fn add_softmax(&mut self, name: &str, input: usize, config: SoftmaxConfig) -> usize {
let node = MlGraphNode {
id: self.nodes.len(),
name: name.to_string(),
operator: MlOperator::Softmax,
inputs: vec![input],
outputs: vec![],
params: MlNodeParams::Softmax(config),
};
self.add_node(node)
}
pub fn topological_sort(&self) -> Vec<usize> {
let n = self.nodes.len();
let mut in_degree = vec![0usize; n];
let mut adj = vec![Vec::new(); n];
for node in &self.nodes {
for &inp in &node.inputs {
if inp < n {
adj[inp].push(node.id);
in_degree[node.id] += 1;
}
}
}
let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
let mut order = Vec::new();
while let Some(u) = queue.pop() {
order.push(u);
for &v in &adj[u] {
in_degree[v] -= 1;
if in_degree[v] == 0 {
queue.push(v);
}
}
}
order
}
}
#[derive(Debug, Clone)]
pub struct QuantizationStrategy {
pub weights: QuantizationType,
pub activations: QuantizationType,
pub kv_cache: Option<QuantizationType>,
pub embedding: Option<QuantizationType>,
pub per_channel: bool,
pub per_token: bool,
pub symmetric: bool,
pub use_gptq: bool,
pub use_awq: bool,
pub group_size: Option<usize>,
}
impl QuantizationStrategy {
pub fn int8_default() -> Self {
Self {
weights: QuantizationType::Int8,
activations: QuantizationType::Int8,
kv_cache: Some(QuantizationType::Fp16),
embedding: None,
per_channel: true,
per_token: false,
symmetric: true,
use_gptq: false,
use_awq: false,
group_size: Some(128),
}
}
pub fn int4_gptq() -> Self {
Self {
weights: QuantizationType::Int4,
activations: QuantizationType::Fp16,
kv_cache: Some(QuantizationType::Fp16),
embedding: Some(QuantizationType::Fp16),
per_channel: true,
per_token: false,
symmetric: false,
use_gptq: true,
use_awq: false,
group_size: Some(128),
}
}
pub fn int4_awq() -> Self {
Self {
weights: QuantizationType::Int4,
activations: QuantizationType::Fp16,
kv_cache: Some(QuantizationType::Fp16),
embedding: Some(QuantizationType::Fp16),
per_channel: true,
per_token: false,
symmetric: false,
use_gptq: false,
use_awq: true,
group_size: Some(128),
}
}
pub fn fp16_default() -> Self {
Self {
weights: QuantizationType::Fp16,
activations: QuantizationType::Fp16,
kv_cache: Some(QuantizationType::Fp16),
embedding: Some(QuantizationType::Fp16),
per_channel: false,
per_token: false,
symmetric: true,
use_gptq: false,
use_awq: false,
group_size: None,
}
}
pub fn model_size_ratio(&self) -> f64 {
let w = self.weights.storage_bytes_per_element();
let a = self.activations.storage_bytes_per_element();
(w + a) / 8.0 }
}
#[derive(Debug, Clone)]
pub struct InferenceSession {
pub model_path: String,
pub num_threads: usize,
pub use_gpu: bool,
pub gpu_device_id: Option<usize>,
pub quantization: Option<QuantizationStrategy>,
pub optimization_level: OptimizationLevel,
pub memory_limit_mb: Option<usize>,
pub log_verbose: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationLevel {
None,
Basic,
Extended,
All,
}
impl Default for InferenceSession {
fn default() -> Self {
Self {
model_path: String::new(),
num_threads: 4,
use_gpu: false,
gpu_device_id: None,
quantization: None,
optimization_level: OptimizationLevel::Extended,
memory_limit_mb: Some(4096),
log_verbose: false,
}
}
}
impl InferenceSession {
pub fn new(model_path: &str) -> Self {
Self {
model_path: model_path.to_string(),
..Default::default()
}
}
pub fn with_quantization(mut self, q: QuantizationStrategy) -> Self {
self.quantization = Some(q);
self
}
pub fn with_gpu(mut self, device_id: usize) -> Self {
self.use_gpu = true;
self.gpu_device_id = Some(device_id);
self
}
}
#[derive(Debug, Clone)]
pub struct MlBenchmarkResult {
pub name: String,
pub operator: Option<MlOperator>,
pub input_shapes: Vec<Vec<usize>>,
pub dtype: MlDataType,
pub latency_mean_us: f64,
pub latency_p50_us: f64,
pub latency_p99_us: f64,
pub throughput_ops_per_sec: f64,
pub gflops: f64,
pub memory_bandwidth_gb_s: f64,
pub peak_memory_mb: f64,
}
impl MlBenchmarkResult {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
operator: None,
input_shapes: vec![],
dtype: MlDataType::Float32,
latency_mean_us: 0.0,
latency_p50_us: 0.0,
latency_p99_us: 0.0,
throughput_ops_per_sec: 0.0,
gflops: 0.0,
memory_bandwidth_gb_s: 0.0,
peak_memory_mb: 0.0,
}
}
pub fn with_operator(mut self, op: MlOperator) -> Self {
self.operator = Some(op);
self
}
pub fn with_shapes(mut self, shapes: Vec<Vec<usize>>) -> Self {
self.input_shapes = shapes;
self
}
pub fn with_dtype(mut self, dtype: MlDataType) -> Self {
self.dtype = dtype;
self
}
}
#[derive(Debug, Clone)]
pub struct MlBenchmarkSuite {
pub results: Vec<MlBenchmarkResult>,
pub cpu_features: CpuFeatures,
pub threading: ThreadingBackend,
}
impl MlBenchmarkSuite {
pub fn new(features: CpuFeatures, threading: ThreadingBackend) -> Self {
Self {
results: Vec::new(),
cpu_features: features,
threading,
}
}
pub fn add_benchmark(&mut self, result: MlBenchmarkResult) {
self.results.push(result);
}
pub fn run_standard_benchmarks(&mut self) {
self.add_benchmark(
MlBenchmarkResult::new("conv2d_resnet50_layer1")
.with_operator(MlOperator::Conv2D)
.with_shapes(vec![vec![1, 64, 56, 56], vec![64, 64, 3, 3]])
.with_dtype(MlDataType::Float32),
);
self.add_benchmark(
MlBenchmarkResult::new("matmul_transformer")
.with_operator(MlOperator::MatMul)
.with_shapes(vec![vec![1, 128, 4096], vec![4096, 4096]])
.with_dtype(MlDataType::Float16),
);
self.add_benchmark(
MlBenchmarkResult::new("attention_llama")
.with_operator(MlOperator::Attention)
.with_shapes(vec![vec![1, 32, 128, 128]])
.with_dtype(MlDataType::BFloat16),
);
self.add_benchmark(
MlBenchmarkResult::new("layernorm_4096")
.with_operator(MlOperator::LayerNorm)
.with_shapes(vec![vec![1, 128, 4096]])
.with_dtype(MlDataType::Float32),
);
self.add_benchmark(
MlBenchmarkResult::new("softmax_4096")
.with_operator(MlOperator::Softmax)
.with_shapes(vec![vec![1, 128, 4096]])
.with_dtype(MlDataType::Float32),
);
self.add_benchmark(
MlBenchmarkResult::new("gelu_4096")
.with_operator(MlOperator::Gelu)
.with_shapes(vec![vec![1, 128, 4096]])
.with_dtype(MlDataType::Float32),
);
self.add_benchmark(
MlBenchmarkResult::new("silu_4096")
.with_operator(MlOperator::Silu)
.with_shapes(vec![vec![1, 128, 4096]])
.with_dtype(MlDataType::Float16),
);
}
pub fn summary(&self) -> String {
let mut s = format!(
"ML Benchmark Suite ({} threads, SIMD: {}b)\n",
self.threading.num_threads(),
self.cpu_features.simd_width()
);
for r in &self.results {
s.push_str(&format!(
" {}: {:.1} us, {:.1} GFLOPS\n",
r.name, r.latency_mean_us, r.gflops
));
}
s
}
}
#[derive(Debug, Clone)]
pub struct LayoutConverter {
pub source_layout: TensorLayout,
pub target_layout: TensorLayout,
pub dtype: MlDataType,
}
impl LayoutConverter {
pub fn new(source: TensorLayout, target: TensorLayout, dtype: MlDataType) -> Self {
Self {
source_layout: source,
target_layout: target,
dtype,
}
}
pub fn convert_strides(&self, shape: &[usize]) -> (Vec<usize>, Vec<usize>) {
let mut src_strides = vec![1usize; shape.len()];
let mut dst_strides = vec![1usize; shape.len()];
for i in (0..shape.len() - 1).rev() {
src_strides[i] = src_strides[i + 1] * shape[i + 1];
dst_strides[i] = dst_strides[i + 1] * shape[i + 1];
}
match (self.source_layout, self.target_layout) {
(TensorLayout::Nchw, TensorLayout::Nhwc) if shape.len() == 4 => {
dst_strides = vec![
shape[1] * shape[2] * shape[3],
1,
shape[1] * shape[3],
shape[1],
];
}
(TensorLayout::Nhwc, TensorLayout::Nchw) if shape.len() == 4 => {
src_strides = vec![
shape[1] * shape[2] * shape[3],
1,
shape[1] * shape[3],
shape[1],
];
}
_ => {}
}
(src_strides, dst_strides)
}
pub fn convert_index(&self, dst_idx: &[usize], shape: &[usize]) -> Vec<usize> {
match (self.source_layout, self.target_layout) {
(TensorLayout::Nchw, TensorLayout::Nhwc) if shape.len() == 4 => {
vec![dst_idx[0], dst_idx[3], dst_idx[1], dst_idx[2]]
}
(TensorLayout::Nhwc, TensorLayout::Nchw) if shape.len() == 4 => {
vec![dst_idx[0], dst_idx[2], dst_idx[3], dst_idx[1]]
}
_ => dst_idx.to_vec(),
}
}
}
#[derive(Debug, Clone)]
pub struct FusedKernel {
pub name: String,
pub operations: Vec<MlOperator>,
pub supported_layouts: Vec<TensorLayout>,
pub min_simd_width: u32,
pub speedup_vs_separate: f64,
}
impl FusedKernel {
pub fn gelu_matmul() -> Self {
Self {
name: "gelu_matmul_fusion".into(),
operations: vec![MlOperator::MatMul, MlOperator::Gelu],
supported_layouts: vec![TensorLayout::Nchw, TensorLayout::Nhwc],
min_simd_width: 128,
speedup_vs_separate: 1.3,
}
}
pub fn conv_bn_relu() -> Self {
Self {
name: "conv_bn_relu_fusion".into(),
operations: vec![
MlOperator::Conv2D,
MlOperator::ElementWise {
name: "BatchNorm".into(),
},
MlOperator::Relu,
],
supported_layouts: vec![TensorLayout::Nchw],
min_simd_width: 128,
speedup_vs_separate: 1.5,
}
}
pub fn layernorm_attention() -> Self {
Self {
name: "layernorm_attention_fusion".into(),
operations: vec![MlOperator::LayerNorm, MlOperator::Attention],
supported_layouts: vec![TensorLayout::Nchw, TensorLayout::Nhwc],
min_simd_width: 256,
speedup_vs_separate: 1.2,
}
}
pub fn rms_norm_rope() -> Self {
Self {
name: "rms_norm_rope_fusion".into(),
operations: vec![MlOperator::RmsNorm, MlOperator::Rope],
supported_layouts: vec![TensorLayout::Nchw],
min_simd_width: 256,
speedup_vs_separate: 1.4,
}
}
pub fn silu_mul() -> Self {
Self {
name: "silu_mul_fusion".into(),
operations: vec![MlOperator::Silu, MlOperator::Mul],
supported_layouts: vec![TensorLayout::Nchw],
min_simd_width: 128,
speedup_vs_separate: 1.6,
}
}
pub fn all_standard_fusions() -> Vec<FusedKernel> {
vec![
Self::gelu_matmul(),
Self::conv_bn_relu(),
Self::layernorm_attention(),
Self::rms_norm_rope(),
Self::silu_mul(),
]
}
}
#[derive(Debug, Clone)]
pub struct ModelProfiler {
pub enabled: bool,
pub record_shapes: bool,
pub record_memory: bool,
pub per_operator_timing: HashMap<String, Vec<f64>>,
pub per_layer_timing: HashMap<usize, f64>,
pub peak_memory_bytes: usize,
pub total_memory_bytes: usize,
}
impl ModelProfiler {
pub fn new() -> Self {
Self {
enabled: true,
record_shapes: true,
record_memory: true,
per_operator_timing: HashMap::new(),
per_layer_timing: HashMap::new(),
peak_memory_bytes: 0,
total_memory_bytes: 0,
}
}
pub fn record_operator(&mut self, name: &str, time_us: f64) {
self.per_operator_timing
.entry(name.to_string())
.or_default()
.push(time_us);
}
pub fn record_layer(&mut self, layer_idx: usize, time_us: f64) {
*self.per_layer_timing.entry(layer_idx).or_insert(0.0) += time_us;
}
pub fn avg_operator_time(&self, name: &str) -> Option<f64> {
self.per_operator_timing
.get(name)
.map(|times| times.iter().sum::<f64>() / times.len() as f64)
}
pub fn total_time(&self) -> f64 {
self.per_layer_timing.values().sum()
}
pub fn bottleneck_operators(&self, top_k: usize) -> Vec<(String, f64)> {
let mut ops: Vec<_> = self
.per_operator_timing
.iter()
.map(|(k, v)| (k.clone(), v.iter().sum::<f64>()))
.collect();
ops.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
ops.truncate(top_k);
ops
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_graph_new() {
let g = MlComputationGraph::new("test_graph");
assert_eq!(g.nodes.len(), 0);
}
#[test]
fn test_graph_add_conv2d() {
let mut g = MlComputationGraph::new("test");
let id = g.add_conv2d("conv1", 0, Conv2DParams::default());
assert_eq!(id, 0);
assert_eq!(g.nodes.len(), 1);
}
#[test]
fn test_graph_topological_sort_simple() {
let mut g = MlComputationGraph::new("test");
let a = g.add_conv2d("a", 0, Conv2DParams::default());
let b = g.add_activation("b", a, ActivationFn::Relu);
let c = g.add_matmul("c", a, b, false, false);
let order = g.topological_sort();
assert_eq!(order.len(), 3);
assert!(
order.iter().position(|&x| x == a).unwrap()
< order.iter().position(|&x| x == b).unwrap()
);
}
#[test]
fn test_graph_add_attention() {
let mut g = MlComputationGraph::new("test");
let attn = g.add_attention("attn", 0, 1, 2, AttentionParams::default());
assert_eq!(attn, 0);
}
#[test]
fn test_quantization_strategy_int8() {
let q = QuantizationStrategy::int8_default();
assert_eq!(q.weights, QuantizationType::Int8);
assert!(q.per_channel);
assert!(q.symmetric);
}
#[test]
fn test_quantization_strategy_gptq() {
let q = QuantizationStrategy::int4_gptq();
assert_eq!(q.weights, QuantizationType::Int4);
assert!(q.use_gptq);
assert!(!q.use_awq);
}
#[test]
fn test_quantization_strategy_size_ratio() {
let q = QuantizationStrategy::int8_default();
assert!(q.model_size_ratio() < 1.0);
let fp16 = QuantizationStrategy::fp16_default();
assert!(fp16.model_size_ratio() < 1.0);
}
#[test]
fn test_inference_session_new() {
let session = InferenceSession::new("model.onnx");
assert_eq!(session.model_path, "model.onnx");
assert_eq!(session.num_threads, 4);
}
#[test]
fn test_inference_session_with_gpu() {
let session = InferenceSession::new("model.onnx").with_gpu(0);
assert!(session.use_gpu);
assert_eq!(session.gpu_device_id, Some(0));
}
#[test]
fn test_benchmark_suite_new() {
let suite = MlBenchmarkSuite::new(
CpuFeatures::detect_x86_modern(),
ThreadingBackend::OpenMp { num_threads: 8 },
);
assert_eq!(suite.threading.num_threads(), 8);
}
#[test]
fn test_benchmark_suite_run_standard() {
let mut suite = MlBenchmarkSuite::new(
CpuFeatures::detect_x86_modern(),
ThreadingBackend::SingleThreaded,
);
suite.run_standard_benchmarks();
assert!(suite.results.len() >= 6);
}
#[test]
fn test_fused_kernel_gelu_matmul() {
let fk = FusedKernel::gelu_matmul();
assert_eq!(fk.name, "gelu_matmul_fusion");
assert!(fk.speedup_vs_separate > 1.0);
}
#[test]
fn test_fused_kernel_all_standard() {
let fusions = FusedKernel::all_standard_fusions();
assert_eq!(fusions.len(), 5);
for f in &fusions {
assert!(!f.operations.is_empty());
assert!(f.speedup_vs_separate > 1.0);
}
}
#[test]
fn test_model_profiler_new() {
let profiler = ModelProfiler::new();
assert!(profiler.enabled);
assert!(profiler.record_shapes);
}
#[test]
fn test_model_profiler_record() {
let mut profiler = ModelProfiler::new();
profiler.record_operator("conv2d", 1500.0);
profiler.record_operator("conv2d", 1400.0);
let avg = profiler.avg_operator_time("conv2d");
assert!(avg.is_some());
assert!((avg.unwrap() - 1450.0).abs() < 1.0);
}
#[test]
fn test_model_profiler_bottleneck() {
let mut profiler = ModelProfiler::new();
profiler.record_operator("conv2d", 5000.0);
profiler.record_operator("matmul", 1000.0);
profiler.record_operator("softmax", 500.0);
let bottlenecks = profiler.bottleneck_operators(2);
assert_eq!(bottlenecks.len(), 2);
assert_eq!(bottlenecks[0].0, "conv2d");
}
#[test]
fn test_layout_converter_nchw_nhwc() {
let conv =
LayoutConverter::new(TensorLayout::Nchw, TensorLayout::Nhwc, MlDataType::Float32);
let idx = conv.convert_index(&[0, 112, 112, 3], &[1, 3, 224, 224]);
assert_eq!(idx, vec![0, 3, 112, 112]);
}
#[test]
fn test_ml_data_type_size() {
assert_eq!(MlDataType::Float32.size_bytes_corrected(), 4);
assert_eq!(MlDataType::Float16.size_bytes_corrected(), 2);
assert_eq!(MlDataType::Int8.size_bytes_corrected(), 1);
assert_eq!(MlDataType::Bool.size_bytes_corrected(), 1);
assert_eq!(MlDataType::Complex128.size_bytes_corrected(), 16);
}
}
#[derive(Debug, Clone)]
pub struct MlCompilerPass {
pub name: String,
pub description: String,
pub enabled: bool,
pub applies_to: Vec<MlOperator>,
}
impl MlCompilerPass {
pub fn constant_folding() -> Self {
Self {
name: "constant_folding".into(),
description: "Fold constant expressions at compile time".into(),
enabled: true,
applies_to: vec![
MlOperator::Add,
MlOperator::Mul,
MlOperator::Sub,
MlOperator::Div,
],
}
}
pub fn operator_fusion() -> Self {
Self {
name: "operator_fusion".into(),
description: "Fuse adjacent operators into single kernels".into(),
enabled: true,
applies_to: vec![
MlOperator::Conv2D,
MlOperator::MatMul,
MlOperator::Gelu,
MlOperator::Relu,
MlOperator::LayerNorm,
],
}
}
pub fn dead_code_elimination() -> Self {
Self {
name: "dce".into(),
description: "Remove unreachable graph nodes".into(),
enabled: true,
applies_to: vec![],
}
}
pub fn memory_planning() -> Self {
Self {
name: "memory_planning".into(),
description: "Optimize memory allocation and reuse".into(),
enabled: true,
applies_to: vec![],
}
}
pub fn layout_optimization() -> Self {
Self {
name: "layout_opt".into(),
description: "Choose optimal data layouts for each operator".into(),
enabled: true,
applies_to: vec![
MlOperator::Conv2D,
MlOperator::MatMul,
MlOperator::Attention,
],
}
}
pub fn quantization_aware() -> Self {
Self {
name: "quantization_aware".into(),
description: "Insert fake quantization nodes for QAT".into(),
enabled: false,
applies_to: vec![MlOperator::MatMul, MlOperator::Conv2D],
}
}
pub fn standard_passes() -> Vec<MlCompilerPass> {
vec![
Self::constant_folding(),
Self::operator_fusion(),
Self::dead_code_elimination(),
Self::memory_planning(),
Self::layout_optimization(),
]
}
}
#[derive(Debug, Clone)]
pub struct MlCompilationPipeline {
pub passes: Vec<MlCompilerPass>,
pub optimization_level: OptimizationLevel,
pub target_features: CpuFeatures,
}
impl MlCompilationPipeline {
pub fn new(level: OptimizationLevel, features: CpuFeatures) -> Self {
let passes = match level {
OptimizationLevel::None => vec![],
OptimizationLevel::Basic => vec![MlCompilerPass::constant_folding()],
OptimizationLevel::Extended => {
let mut p = MlCompilerPass::standard_passes();
p.push(MlCompilerPass::quantization_aware());
p
}
OptimizationLevel::All => {
let mut p = MlCompilerPass::standard_passes();
p.push(MlCompilerPass::quantization_aware());
p.push(MlCompilerPass {
name: "loop_unrolling".into(),
description: "Unroll inner loops for GEMM/Conv".into(),
enabled: true,
applies_to: vec![MlOperator::MatMul, MlOperator::Conv2D],
});
p.push(MlCompilerPass {
name: "vectorization".into(),
description: "Generate SIMD vectorized code".into(),
enabled: true,
applies_to: vec![],
});
p
}
};
Self {
passes,
optimization_level: level,
target_features: features,
}
}
pub fn enabled_pass_count(&self) -> usize {
self.passes.iter().filter(|p| p.enabled).count()
}
pub fn pass_names(&self) -> Vec<String> {
self.passes.iter().map(|p| p.name.clone()).collect()
}
}
#[derive(Debug, Clone)]
pub struct DataPipeline {
pub steps: Vec<PreprocessingStep>,
pub input_dtype: MlDataType,
pub output_dtype: MlDataType,
}
#[derive(Debug, Clone)]
pub enum PreprocessingStep {
Resize {
height: usize,
width: usize,
method: ResizeMethod,
},
Normalize {
mean: Vec<f64>,
std: Vec<f64>,
},
CenterCrop {
height: usize,
width: usize,
},
RandomHorizontalFlip {
probability: f64,
},
ColorJitter {
brightness: f64,
contrast: f64,
saturation: f64,
hue: f64,
},
ToTensor,
Normalize01,
Pad {
top: usize,
bottom: usize,
left: usize,
right: usize,
value: f64,
},
ConvertColor {
from: ColorSpace,
to: ColorSpace,
},
Tokenize {
vocab_size: usize,
max_length: usize,
add_special_tokens: bool,
},
AudioResample {
target_sample_rate: u32,
},
AudioMelSpectrogram {
n_fft: usize,
hop_length: usize,
n_mels: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeMethod {
Nearest,
Bilinear,
Bicubic,
Lanczos,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSpace {
Rgb,
Bgr,
Gray,
Yuv,
Hsv,
Lab,
}
impl DataPipeline {
pub fn new(input: MlDataType, output: MlDataType) -> Self {
Self {
steps: Vec::new(),
input_dtype: input,
output_dtype: output,
}
}
pub fn imagenet_preprocessing() -> Self {
Self {
steps: vec![
PreprocessingStep::Resize {
height: 256,
width: 256,
method: ResizeMethod::Bilinear,
},
PreprocessingStep::CenterCrop {
height: 224,
width: 224,
},
PreprocessingStep::ToTensor,
PreprocessingStep::Normalize {
mean: vec![0.485, 0.456, 0.406],
std: vec![0.229, 0.224, 0.225],
},
],
input_dtype: MlDataType::Uint8,
output_dtype: MlDataType::Float32,
}
}
pub fn clip_preprocessing() -> Self {
Self {
steps: vec![
PreprocessingStep::Resize {
height: 224,
width: 224,
method: ResizeMethod::Bicubic,
},
PreprocessingStep::ToTensor,
PreprocessingStep::Normalize {
mean: vec![0.48145466, 0.4578275, 0.40821073],
std: vec![0.26862954, 0.26130258, 0.27577711],
},
],
input_dtype: MlDataType::Uint8,
output_dtype: MlDataType::Float32,
}
}
pub fn whisper_audio_preprocessing() -> Self {
Self {
steps: vec![
PreprocessingStep::AudioResample {
target_sample_rate: 16000,
},
PreprocessingStep::AudioMelSpectrogram {
n_fft: 400,
hop_length: 160,
n_mels: 80,
},
PreprocessingStep::Normalize01,
],
input_dtype: MlDataType::Float32,
output_dtype: MlDataType::Float32,
}
}
pub fn tokenizer_preprocessing(vocab_size: usize, max_length: usize) -> Self {
Self {
steps: vec![PreprocessingStep::Tokenize {
vocab_size,
max_length,
add_special_tokens: true,
}],
input_dtype: MlDataType::Int32,
output_dtype: MlDataType::Int64,
}
}
}
#[derive(Debug, Clone)]
pub struct MlMetrics {
pub accuracy: Option<f64>,
pub precision: Option<f64>,
pub recall: Option<f64>,
pub f1_score: Option<f64>,
pub perplexity: Option<f64>,
pub bleu_score: Option<f64>,
pub rouge_l_score: Option<f64>,
pub wer: Option<f64>, pub cer: Option<f64>, pub fid: Option<f64>, pub clip_score: Option<f64>,
pub mse: Option<f64>,
pub mae: Option<f64>,
pub top1_accuracy: Option<f64>,
pub top5_accuracy: Option<f64>,
}
impl Default for MlMetrics {
fn default() -> Self {
Self {
accuracy: None,
precision: None,
recall: None,
f1_score: None,
perplexity: None,
bleu_score: None,
rouge_l_score: None,
wer: None,
cer: None,
fid: None,
clip_score: None,
mse: None,
mae: None,
top1_accuracy: None,
top5_accuracy: None,
}
}
}
impl MlMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn with_accuracy(mut self, acc: f64) -> Self {
self.accuracy = Some(acc);
self
}
pub fn with_perplexity(mut self, ppl: f64) -> Self {
self.perplexity = Some(ppl);
self
}
pub fn with_wer(mut self, wer: f64) -> Self {
self.wer = Some(wer);
self
}
pub fn with_fid(mut self, fid: f64) -> Self {
self.fid = Some(fid);
self
}
pub fn has_any(&self) -> bool {
self.accuracy.is_some()
|| self.perplexity.is_some()
|| self.wer.is_some()
|| self.fid.is_some()
|| self.mse.is_some()
|| self.top1_accuracy.is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MlHardwareTarget {
CpuX86,
CpuArm,
CpuRiscV,
GpuCuda { compute_capability: (u32, u32) },
GpuRocm { gfx_arch: String },
GpuMetal { family: u32 },
GpuVulkan,
GpuOpenCL,
NpuIntel,
NpuQualcomm,
NpuApple,
TpuEdge,
TpuCloud,
Fpga,
Asic,
Custom { name: String },
}
impl MlHardwareTarget {
pub fn is_gpu(&self) -> bool {
matches!(
self,
Self::GpuCuda { .. }
| Self::GpuRocm { .. }
| Self::GpuMetal { .. }
| Self::GpuVulkan
| Self::GpuOpenCL
)
}
pub fn is_npu(&self) -> bool {
matches!(self, Self::NpuIntel | Self::NpuQualcomm | Self::NpuApple)
}
pub fn recommended_precision(&self) -> QuantizationType {
match self {
Self::CpuX86 if true => QuantizationType::Int8,
Self::CpuArm => QuantizationType::Int8,
Self::GpuCuda { .. } => QuantizationType::Fp16,
Self::GpuMetal { .. } => QuantizationType::Fp16,
Self::NpuApple => QuantizationType::Fp16,
_ => QuantizationType::Fp32,
}
}
}
#[derive(Debug, Clone)]
pub struct ModelCard {
pub name: String,
pub version: String,
pub architecture: String,
pub param_count: usize,
pub framework: String,
pub task: MlTask,
pub languages: Vec<String>,
pub license: String,
pub metrics: MlMetrics,
pub hardware_requirements: MlHardwareRequirements,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MlTask {
TextGeneration,
TextClassification,
TokenClassification,
QuestionAnswering,
Summarization,
Translation,
ImageClassification,
ObjectDetection,
ImageSegmentation,
ImageGeneration,
ImageToImage,
SpeechRecognition,
TextToSpeech,
AudioClassification,
VideoClassification,
VideoGeneration,
MultiModal,
}
#[derive(Debug, Clone)]
pub struct MlHardwareRequirements {
pub min_ram_gb: f64,
pub min_vram_gb: Option<f64>,
pub recommended_ram_gb: f64,
pub min_storage_gb: f64,
pub cpu_requirements: Vec<String>,
}
impl Default for MlHardwareRequirements {
fn default() -> Self {
Self {
min_ram_gb: 8.0,
min_vram_gb: None,
recommended_ram_gb: 16.0,
min_storage_gb: 10.0,
cpu_requirements: vec!["AVX2".into()],
}
}
}
impl ModelCard {
pub fn new(name: &str, architecture: &str, task: MlTask) -> Self {
Self {
name: name.to_string(),
version: "1.0.0".to_string(),
architecture: architecture.to_string(),
param_count: 0,
framework: "unknown".to_string(),
task,
languages: vec!["en".to_string()],
license: "Apache-2.0".to_string(),
metrics: MlMetrics::default(),
hardware_requirements: MlHardwareRequirements::default(),
}
}
pub fn with_params(mut self, count: usize) -> Self {
self.param_count = count;
self
}
pub fn with_framework(mut self, fw: &str) -> Self {
self.framework = fw.to_string();
self
}
pub fn llama3_8b() -> Self {
Self::new("Llama-3-8B", "Llama", MlTask::TextGeneration)
.with_params(8_030_000_000)
.with_framework("llama.cpp")
}
pub fn whisper_large_v3() -> Self {
Self::new("Whisper-Large-v3", "Whisper", MlTask::SpeechRecognition)
.with_params(1_550_000_000)
.with_framework("whisper.cpp")
}
pub fn sd_xl_base() -> Self {
Self::new(
"SDXL-Base-1.0",
"Stable Diffusion XL",
MlTask::ImageGeneration,
)
.with_params(2_600_000_000)
.with_framework("stable-diffusion.cpp")
}
pub fn resnet50() -> Self {
Self::new("ResNet-50", "ResNet", MlTask::ImageClassification)
.with_params(25_557_032)
.with_framework("ONNX Runtime")
}
pub fn mobilenet_v2() -> Self {
Self::new("MobileNetV2", "MobileNetV2", MlTask::ImageClassification)
.with_params(3_504_872)
.with_framework("TensorFlow Lite")
}
}