use std::collections::HashMap;
use std::fmt;
pub const AVX512_FP32_LANES: usize = 16;
pub const AVX2_FP32_LANES: usize = 8;
pub const SSE_FP32_LANES: usize = 4;
pub const AVX512_BF16_LANES: usize = 32;
pub const AVX512_FP16_LANES: usize = 32;
pub const VNNI_INT8_LANES: usize = 64;
pub const AMX_MAX_ROWS: usize = 16;
pub const AMX_MAX_COL_BYTES: usize = 64;
pub const GEMM_BLOCK_M: usize = 64;
pub const GEMM_BLOCK_N: usize = 256;
pub const GEMM_BLOCK_K: usize = 64;
pub const CONV_BLOCK_H: usize = 8;
pub const CONV_BLOCK_W: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MLTargetFeature {
SSE2,
AVX,
AVX2,
AVX512F,
AVX512VNNI,
AVX512BF16,
AVX512FP16,
AMXINT8,
AMXBF16,
AMXFP16,
}
impl MLTargetFeature {
pub fn isa_level(&self) -> &'static str {
match self {
MLTargetFeature::SSE2 => "sse2",
MLTargetFeature::AVX => "avx",
MLTargetFeature::AVX2 => "avx2",
MLTargetFeature::AVX512F => "avx512f",
MLTargetFeature::AVX512VNNI => "avx512vnni",
MLTargetFeature::AVX512BF16 => "avx512bf16",
MLTargetFeature::AVX512FP16 => "avx512fp16",
MLTargetFeature::AMXINT8 => "amx-int8",
MLTargetFeature::AMXBF16 => "amx-bf16",
MLTargetFeature::AMXFP16 => "amx-fp16",
}
}
pub fn is_at_least(&self, other: MLTargetFeature) -> bool {
self.rank() >= other.rank()
}
fn rank(&self) -> u32 {
match self {
MLTargetFeature::SSE2 => 0,
MLTargetFeature::AVX => 1,
MLTargetFeature::AVX2 => 2,
MLTargetFeature::AVX512F => 3,
MLTargetFeature::AVX512VNNI => 4,
MLTargetFeature::AVX512BF16 => 5,
MLTargetFeature::AVX512FP16 => 6,
MLTargetFeature::AMXINT8 => 7,
MLTargetFeature::AMXBF16 => 8,
MLTargetFeature::AMXFP16 => 9,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MLOpKind {
MatMul,
MatMulTransposeA,
MatMulTransposeB,
Conv2D,
Conv3D,
Conv2DDepthwise,
Conv2DGrouped,
ConvTranspose2D,
ReLU,
ReLU6,
LeakyReLU,
PReLU,
Sigmoid,
Tanh,
GELU,
SiLU,
Softmax,
LogSoftmax,
HardSwish,
Mish,
MaxPool2D,
AvgPool2D,
GlobalMaxPool2D,
GlobalAvgPool2D,
AdaptiveAvgPool2D,
MaxPool3D,
BatchNorm,
LayerNorm,
GroupNorm,
InstanceNorm,
RMSNorm,
ScaledDotProductAttention,
MultiHeadAttention,
FlashAttention,
EmbeddingLookup,
EmbeddingBag,
Gather,
Scatter,
GatherND,
ReduceSum,
ReduceMean,
ReduceMax,
ReduceMin,
ReduceProd,
Add,
Mul,
Sub,
Div,
Pow,
Sqrt,
Exp,
Log,
Concat,
Slice,
Reshape,
Transpose,
Pad,
Resize,
}
impl MLOpKind {
pub fn name(&self) -> &'static str {
match self {
MLOpKind::MatMul => "MatMul",
MLOpKind::MatMulTransposeA => "MatMulTransposeA",
MLOpKind::MatMulTransposeB => "MatMulTransposeB",
MLOpKind::Conv2D => "Conv2D",
MLOpKind::Conv3D => "Conv3D",
MLOpKind::Conv2DDepthwise => "Conv2DDepthwise",
MLOpKind::Conv2DGrouped => "Conv2DGrouped",
MLOpKind::ConvTranspose2D => "ConvTranspose2D",
MLOpKind::ReLU => "ReLU",
MLOpKind::ReLU6 => "ReLU6",
MLOpKind::LeakyReLU => "LeakyReLU",
MLOpKind::PReLU => "PReLU",
MLOpKind::Sigmoid => "Sigmoid",
MLOpKind::Tanh => "Tanh",
MLOpKind::GELU => "GELU",
MLOpKind::SiLU => "SiLU",
MLOpKind::Softmax => "Softmax",
MLOpKind::LogSoftmax => "LogSoftmax",
MLOpKind::HardSwish => "HardSwish",
MLOpKind::Mish => "Mish",
MLOpKind::MaxPool2D => "MaxPool2D",
MLOpKind::AvgPool2D => "AvgPool2D",
MLOpKind::GlobalMaxPool2D => "GlobalMaxPool2D",
MLOpKind::GlobalAvgPool2D => "GlobalAvgPool2D",
MLOpKind::AdaptiveAvgPool2D => "AdaptiveAvgPool2D",
MLOpKind::MaxPool3D => "MaxPool3D",
MLOpKind::BatchNorm => "BatchNorm",
MLOpKind::LayerNorm => "LayerNorm",
MLOpKind::GroupNorm => "GroupNorm",
MLOpKind::InstanceNorm => "InstanceNorm",
MLOpKind::RMSNorm => "RMSNorm",
MLOpKind::ScaledDotProductAttention => "ScaledDotProductAttention",
MLOpKind::MultiHeadAttention => "MultiHeadAttention",
MLOpKind::FlashAttention => "FlashAttention",
MLOpKind::EmbeddingLookup => "EmbeddingLookup",
MLOpKind::EmbeddingBag => "EmbeddingBag",
MLOpKind::Gather => "Gather",
MLOpKind::Scatter => "Scatter",
MLOpKind::GatherND => "GatherND",
MLOpKind::ReduceSum => "ReduceSum",
MLOpKind::ReduceMean => "ReduceMean",
MLOpKind::ReduceMax => "ReduceMax",
MLOpKind::ReduceMin => "ReduceMin",
MLOpKind::ReduceProd => "ReduceProd",
MLOpKind::Add => "Add",
MLOpKind::Mul => "Mul",
MLOpKind::Sub => "Sub",
MLOpKind::Div => "Div",
MLOpKind::Pow => "Pow",
MLOpKind::Sqrt => "Sqrt",
MLOpKind::Exp => "Exp",
MLOpKind::Log => "Log",
MLOpKind::Concat => "Concat",
MLOpKind::Slice => "Slice",
MLOpKind::Reshape => "Reshape",
MLOpKind::Transpose => "Transpose",
MLOpKind::Pad => "Pad",
MLOpKind::Resize => "Resize",
}
}
pub fn is_activation(&self) -> bool {
matches!(
self,
MLOpKind::ReLU
| MLOpKind::ReLU6
| MLOpKind::LeakyReLU
| MLOpKind::PReLU
| MLOpKind::Sigmoid
| MLOpKind::Tanh
| MLOpKind::GELU
| MLOpKind::SiLU
| MLOpKind::Softmax
| MLOpKind::LogSoftmax
| MLOpKind::HardSwish
| MLOpKind::Mish
)
}
pub fn is_normalization(&self) -> bool {
matches!(
self,
MLOpKind::BatchNorm
| MLOpKind::LayerNorm
| MLOpKind::GroupNorm
| MLOpKind::InstanceNorm
| MLOpKind::RMSNorm
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MLOptLevel {
O0,
O1,
O2,
O3,
}
impl Default for MLOptLevel {
fn default() -> Self {
MLOptLevel::O2
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TensorShape {
pub dims: Vec<usize>,
}
impl TensorShape {
pub fn new(dims: Vec<usize>) -> Self {
Self { dims }
}
pub fn from_slice(dims: &[usize]) -> Self {
Self {
dims: dims.to_vec(),
}
}
pub fn rank(&self) -> usize {
self.dims.len()
}
pub fn numel(&self) -> usize {
self.dims.iter().product()
}
pub fn is_scalar(&self) -> bool {
self.dims.is_empty() || (self.dims.len() == 1 && self.dims[0] == 1)
}
pub fn dim(&self, axis: usize) -> Option<usize> {
self.dims.get(axis).copied()
}
pub fn batch(&self) -> Option<usize> {
self.dims.first().copied()
}
pub fn as_nchw(&self) -> Option<(usize, usize, usize, usize)> {
if self.dims.len() == 4 {
Some((self.dims[0], self.dims[1], self.dims[2], self.dims[3]))
} else {
None
}
}
pub fn as_nhwc(&self) -> Option<(usize, usize, usize, usize)> {
if self.dims.len() == 4 {
Some((self.dims[0], self.dims[1], self.dims[2], self.dims[3]))
} else {
None
}
}
}
impl fmt::Display for TensorShape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
for (i, d) in self.dims.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", d)?;
}
write!(f, "]")
}
}
pub struct X86MLSupport {
pub features: Vec<MLTargetFeature>,
pub opt_level: MLOptLevel,
pub tensor_ops: X86TensorOps,
pub vector_ml: X86VectorML,
pub intrinsics: X86MLIntrinsics,
pub data_types: X86MLDataType,
pub layout: X86MLLayout,
pub graph: X86MLGraph,
pub quantization: X86MLQuantization,
pub runtime: X86MLRuntime,
pub stats: MLStats,
}
#[derive(Debug, Clone, Default)]
pub struct MLStats {
pub ops_lowered: usize,
pub fused_ops: usize,
pub vectorized_ops: usize,
pub quantized_ops: usize,
pub amx_ops: usize,
pub vnni_ops: usize,
pub bf16_ops: usize,
pub fp16_ops: usize,
pub total_flops_estimated: u64,
pub memory_saved_bytes: u64,
}
impl X86MLSupport {
pub fn new(features: Vec<MLTargetFeature>, opt_level: MLOptLevel) -> Self {
let max_feature = features
.iter()
.max_by_key(|f| f.rank())
.copied()
.unwrap_or(MLTargetFeature::SSE2);
Self {
features,
opt_level,
tensor_ops: X86TensorOps::new(max_feature),
vector_ml: X86VectorML::new(max_feature),
intrinsics: X86MLIntrinsics::new(max_feature),
data_types: X86MLDataType::new(),
layout: X86MLLayout::new(),
graph: X86MLGraph::new(max_feature, opt_level),
quantization: X86MLQuantization::new(),
runtime: X86MLRuntime::new(),
stats: MLStats::default(),
}
}
pub fn has_feature(&self, feature: MLTargetFeature) -> bool {
self.features.contains(&feature)
}
pub fn max_feature(&self) -> MLTargetFeature {
self.features
.iter()
.max_by_key(|f| f.rank())
.copied()
.unwrap_or(MLTargetFeature::SSE2)
}
pub fn lower_tensor_op(
&mut self,
op: MLOpKind,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
) -> Result<TensorOpResult, MLError> {
self.stats.ops_lowered += 1;
self.tensor_ops
.lower(op, input_shape, weight_shape, output_shape, &self.features)
}
pub fn try_fuse_ops(&mut self, ops: &[MLOpKind]) -> Option<FusedOp> {
if let Some(fused) = self.graph.try_fuse(ops) {
self.stats.fused_ops += 1;
Some(fused)
} else {
None
}
}
pub fn vectorize_op(
&mut self,
op: MLOpKind,
dtype: MLDataType,
) -> Result<Vec<VectorizedKernel>, MLError> {
self.stats.vectorized_ops += 1;
self.vector_ml.vectorize(op, dtype, &self.features)
}
pub fn quantize_op(
&mut self,
op: MLOpKind,
src_dtype: MLDataType,
dst_dtype: MLDataType,
) -> Result<QuantizedOp, MLError> {
self.stats.quantized_ops += 1;
self.quantization.quantize(op, src_dtype, dst_dtype)
}
pub fn summary(&self) -> String {
format!(
"ML Compilation Statistics:\n\
Operations lowered: {}\n\
Fused ops: {}\n\
Vectorized ops: {}\n\
Quantized ops: {}\n\
AMX ops: {}\n\
VNNI ops: {}\n\
BF16 ops: {}\n\
FP16 ops: {}\n\
Est. FLOPS: {}\n\
Memory saved: {} bytes",
self.stats.ops_lowered,
self.stats.fused_ops,
self.stats.vectorized_ops,
self.stats.quantized_ops,
self.stats.amx_ops,
self.stats.vnni_ops,
self.stats.bf16_ops,
self.stats.fp16_ops,
self.stats.total_flops_estimated,
self.stats.memory_saved_bytes,
)
}
}
#[derive(Debug, Clone)]
pub struct TensorOpResult {
pub op: MLOpKind,
pub kernel_name: String,
pub flops: u64,
pub bytes_read: u64,
pub bytes_written: u64,
pub vectorized: bool,
pub isa_used: Option<MLTargetFeature>,
pub block_sizes: Option<(usize, usize, usize)>,
}
#[derive(Debug, Clone)]
pub struct FusedOp {
pub name: String,
pub ops: Vec<MLOpKind>,
pub fused_kernel: String,
pub estimated_speedup: f64,
}
#[derive(Debug, Clone)]
pub enum MLError {
UnsupportedOp(MLOpKind),
UnsupportedShape(String),
UnsupportedDataType(MLDataType),
FeatureNotAvailable(MLTargetFeature),
LoweringFailed(String),
QuantizationFailed(String),
VectorizationFailed(String),
FusionFailed(String),
Internal(String),
}
impl fmt::Display for MLError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MLError::UnsupportedOp(op) => write!(f, "Unsupported ML operation: {}", op.name()),
MLError::UnsupportedShape(s) => write!(f, "Unsupported tensor shape: {}", s),
MLError::UnsupportedDataType(dt) => write!(f, "Unsupported data type: {:?}", dt),
MLError::FeatureNotAvailable(feat) => {
write!(f, "Target feature not available: {:?}", feat)
}
MLError::LoweringFailed(s) => write!(f, "Lowering failed: {}", s),
MLError::QuantizationFailed(s) => write!(f, "Quantization failed: {}", s),
MLError::VectorizationFailed(s) => write!(f, "Vectorization failed: {}", s),
MLError::FusionFailed(s) => write!(f, "Fusion failed: {}", s),
MLError::Internal(s) => write!(f, "Internal error: {}", s),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MLDataType {
FP32,
FP16,
BF16,
FP8E4M3,
FP8E5M2,
INT8,
UINT8,
INT4,
UINT4,
INT2,
INT1,
INT16,
INT32,
INT64,
BOOL,
}
impl MLDataType {
pub fn size_bytes(&self) -> usize {
match self {
MLDataType::FP32 => 4,
MLDataType::FP16 => 2,
MLDataType::BF16 => 2,
MLDataType::FP8E4M3 => 1,
MLDataType::FP8E5M2 => 1,
MLDataType::INT8 => 1,
MLDataType::UINT8 => 1,
MLDataType::INT4 => 1, MLDataType::UINT4 => 1, MLDataType::INT2 => 1, MLDataType::INT1 => 1, MLDataType::INT16 => 2,
MLDataType::INT32 => 4,
MLDataType::INT64 => 8,
MLDataType::BOOL => 1,
}
}
pub fn size_bits(&self) -> usize {
self.size_bytes() * 8
}
pub fn elem_bits(&self) -> usize {
match self {
MLDataType::FP32 => 32,
MLDataType::FP16 => 16,
MLDataType::BF16 => 16,
MLDataType::FP8E4M3 => 8,
MLDataType::FP8E5M2 => 8,
MLDataType::INT8 => 8,
MLDataType::UINT8 => 8,
MLDataType::INT4 => 4,
MLDataType::UINT4 => 4,
MLDataType::INT2 => 2,
MLDataType::INT1 => 1,
MLDataType::INT16 => 16,
MLDataType::INT32 => 32,
MLDataType::INT64 => 64,
MLDataType::BOOL => 1,
}
}
pub fn elements_per_byte(&self) -> usize {
match self {
MLDataType::INT4 | MLDataType::UINT4 => 2,
MLDataType::INT2 => 4,
MLDataType::INT1 | MLDataType::BOOL => 8,
_ => {
if self.size_bytes() >= 1 {
1
} else {
1
}
}
}
}
pub fn is_floating(&self) -> bool {
matches!(
self,
MLDataType::FP32
| MLDataType::FP16
| MLDataType::BF16
| MLDataType::FP8E4M3
| MLDataType::FP8E5M2
)
}
pub fn is_integer(&self) -> bool {
matches!(
self,
MLDataType::INT8
| MLDataType::UINT8
| MLDataType::INT4
| MLDataType::UINT4
| MLDataType::INT2
| MLDataType::INT1
| MLDataType::INT16
| MLDataType::INT32
| MLDataType::INT64
| MLDataType::BOOL
)
}
pub fn is_quantized(&self) -> bool {
matches!(
self,
MLDataType::INT8
| MLDataType::UINT8
| MLDataType::INT4
| MLDataType::UINT4
| MLDataType::INT2
| MLDataType::INT1
)
}
pub fn is_signed(&self) -> bool {
matches!(
self,
MLDataType::INT8
| MLDataType::INT4
| MLDataType::INT2
| MLDataType::INT16
| MLDataType::INT32
| MLDataType::INT64
)
}
}
impl fmt::Display for MLDataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MLDataType::FP32 => write!(f, "fp32"),
MLDataType::FP16 => write!(f, "fp16"),
MLDataType::BF16 => write!(f, "bf16"),
MLDataType::FP8E4M3 => write!(f, "fp8_e4m3"),
MLDataType::FP8E5M2 => write!(f, "fp8_e5m2"),
MLDataType::INT8 => write!(f, "int8"),
MLDataType::UINT8 => write!(f, "uint8"),
MLDataType::INT4 => write!(f, "int4"),
MLDataType::UINT4 => write!(f, "uint4"),
MLDataType::INT2 => write!(f, "int2"),
MLDataType::INT1 => write!(f, "int1"),
MLDataType::INT16 => write!(f, "int16"),
MLDataType::INT32 => write!(f, "int32"),
MLDataType::INT64 => write!(f, "int64"),
MLDataType::BOOL => write!(f, "bool"),
}
}
}
pub struct X86MLDataType {
conversion_table: HashMap<(MLDataType, MLDataType), TypeConversion>,
dtype_features: HashMap<MLDataType, Vec<MLTargetFeature>>,
}
#[derive(Debug, Clone)]
pub struct TypeConversion {
pub from: MLDataType,
pub to: MLDataType,
pub instr_sequence: Vec<String>,
pub throughput_hint: f64,
pub is_lossy: bool,
}
impl X86MLDataType {
pub fn new() -> Self {
let mut s = Self {
conversion_table: HashMap::new(),
dtype_features: HashMap::new(),
};
s.init_conversion_table();
s.init_dtype_features();
s
}
fn init_conversion_table(&mut self) {
self.conversion_table.insert(
(MLDataType::FP32, MLDataType::FP16),
TypeConversion {
from: MLDataType::FP32,
to: MLDataType::FP16,
instr_sequence: vec!["vcvtps2ph".into(), "vmovdqu".into()],
throughput_hint: 0.5,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::FP16, MLDataType::FP32),
TypeConversion {
from: MLDataType::FP16,
to: MLDataType::FP32,
instr_sequence: vec!["vcvtph2ps".into(), "vmovups".into()],
throughput_hint: 0.5,
is_lossy: false,
},
);
self.conversion_table.insert(
(MLDataType::FP32, MLDataType::BF16),
TypeConversion {
from: MLDataType::FP32,
to: MLDataType::BF16,
instr_sequence: vec!["vcvtne2ps2bf16".into()],
throughput_hint: 1.0,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::BF16, MLDataType::FP32),
TypeConversion {
from: MLDataType::BF16,
to: MLDataType::FP32,
instr_sequence: vec!["vpmovzxwd".into(), "vpslld $16, %xmm0, %xmm0".into()],
throughput_hint: 0.5,
is_lossy: false,
},
);
self.conversion_table.insert(
(MLDataType::FP32, MLDataType::INT8),
TypeConversion {
from: MLDataType::FP32,
to: MLDataType::INT8,
instr_sequence: vec![
"vmulps {scale}".into(),
"vroundps".into(),
"vcvtps2dq".into(),
"vpackssdw".into(),
"vpacksswb".into(),
],
throughput_hint: 1.0,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::INT8, MLDataType::FP32),
TypeConversion {
from: MLDataType::INT8,
to: MLDataType::FP32,
instr_sequence: vec![
"vpmovsxbd".into(),
"vcvtdq2ps".into(),
"vmulps {inv_scale}".into(),
],
throughput_hint: 1.0,
is_lossy: false,
},
);
self.conversion_table.insert(
(MLDataType::FP16, MLDataType::BF16),
TypeConversion {
from: MLDataType::FP16,
to: MLDataType::BF16,
instr_sequence: vec!["vcvtph2ps".into(), "vcvtne2ps2bf16".into()],
throughput_hint: 2.0,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::BF16, MLDataType::FP16),
TypeConversion {
from: MLDataType::BF16,
to: MLDataType::FP16,
instr_sequence: vec!["vpmovzxwd".into(), "vpslld $16".into(), "vcvtps2ph".into()],
throughput_hint: 2.0,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::FP32, MLDataType::FP8E4M3),
TypeConversion {
from: MLDataType::FP32,
to: MLDataType::FP8E4M3,
instr_sequence: vec![
"vcvtne2ps2bf16".into(), "vpsrld $8".into(), "vpackusdw".into(),
"vpackuswb".into(),
],
throughput_hint: 2.0,
is_lossy: true,
},
);
self.conversion_table.insert(
(MLDataType::INT8, MLDataType::INT4),
TypeConversion {
from: MLDataType::INT8,
to: MLDataType::INT4,
instr_sequence: vec!["vpacksswb".into()],
throughput_hint: 0.5,
is_lossy: true,
},
);
}
fn init_dtype_features(&mut self) {
self.dtype_features
.insert(MLDataType::FP32, vec![MLTargetFeature::SSE2]);
self.dtype_features
.insert(MLDataType::FP16, vec![MLTargetFeature::AVX512FP16]);
self.dtype_features
.insert(MLDataType::BF16, vec![MLTargetFeature::AVX512BF16]);
self.dtype_features
.insert(MLDataType::FP8E4M3, vec![MLTargetFeature::AVX512FP16]);
self.dtype_features
.insert(MLDataType::FP8E5M2, vec![MLTargetFeature::AVX512FP16]);
self.dtype_features
.insert(MLDataType::INT8, vec![MLTargetFeature::AVX512VNNI]);
self.dtype_features
.insert(MLDataType::INT4, vec![MLTargetFeature::AVX512VNNI]);
}
pub fn get_conversion(&self, from: MLDataType, to: MLDataType) -> Option<&TypeConversion> {
self.conversion_table.get(&(from, to))
}
pub fn can_convert(&self, from: MLDataType, to: MLDataType) -> bool {
self.conversion_table.contains_key(&(from, to))
}
pub fn required_features(&self, dtype: MLDataType) -> &[MLTargetFeature] {
self.dtype_features
.get(&dtype)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn optimal_compute_dtype(&self, features: &[MLTargetFeature]) -> MLDataType {
if features.contains(&MLTargetFeature::AVX512VNNI) {
MLDataType::INT8
} else if features.contains(&MLTargetFeature::AVX512BF16) {
MLDataType::BF16
} else if features.contains(&MLTargetFeature::AVX512FP16) {
MLDataType::FP16
} else {
MLDataType::FP32
}
}
pub fn accumulator_dtype(&self, compute_dtype: MLDataType) -> MLDataType {
match compute_dtype {
MLDataType::INT8 | MLDataType::UINT8 => MLDataType::INT32,
MLDataType::INT4 | MLDataType::UINT4 => MLDataType::INT32,
MLDataType::FP16 | MLDataType::BF16 => MLDataType::FP32,
_ => MLDataType::FP32,
}
}
}
impl Default for X86MLDataType {
fn default() -> Self {
Self::new()
}
}
pub struct X86TensorOps {
max_feature: MLTargetFeature,
kernel_cache: HashMap<(MLOpKind, String), TensorOpResult>,
gemm_micro_kernels: Vec<GEMMMicroKernel>,
winograd_configs: HashMap<(usize, usize), WinogradConfig>,
tuning_history: Vec<TuningEntry>,
}
#[derive(Debug, Clone)]
pub struct GEMMMicroKernel {
pub name: String,
pub m_block: usize,
pub n_block: usize,
pub k_block: usize,
pub a_dtype: MLDataType,
pub b_dtype: MLDataType,
pub c_dtype: MLDataType,
pub requires_feature: MLTargetFeature,
pub flops_per_cycle: f64,
pub bytes_per_cycle: f64,
}
#[derive(Debug, Clone)]
pub struct WinogradConfig {
pub f_m: usize,
pub f_r: usize,
pub tile_size: usize,
pub transform_a: Vec<f32>,
pub transform_b: Vec<f32>,
pub transform_c: Vec<f32>,
}
#[derive(Debug, Clone)]
pub struct TuningEntry {
pub op: MLOpKind,
pub shape_signature: String,
pub kernel_name: String,
pub runtime_us: f64,
pub flops: u64,
pub efficiency_pct: f64,
}
impl X86TensorOps {
pub fn new(max_feature: MLTargetFeature) -> Self {
let mut ops = Self {
max_feature,
kernel_cache: HashMap::new(),
gemm_micro_kernels: Vec::new(),
winograd_configs: HashMap::new(),
tuning_history: Vec::new(),
};
ops.init_gemm_kernels();
ops.init_winograd_configs();
ops
}
pub fn lower(
&mut self,
op: MLOpKind,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let cache_key = self.shape_signature(op, input_shape, weight_shape, output_shape);
if let Some(cached) = self.kernel_cache.get(&(op, cache_key.clone())) {
return Ok(cached.clone());
}
let result = match op {
MLOpKind::MatMul | MLOpKind::MatMulTransposeA | MLOpKind::MatMulTransposeB => {
self.lower_gemm(op, input_shape, weight_shape, output_shape, features)?
}
MLOpKind::Conv2D | MLOpKind::Conv2DDepthwise | MLOpKind::Conv2DGrouped => {
self.lower_conv2d(op, input_shape, weight_shape, output_shape, features)?
}
MLOpKind::Conv3D => {
self.lower_conv3d(input_shape, weight_shape, output_shape, features)?
}
MLOpKind::ConvTranspose2D => {
self.lower_conv_transpose(input_shape, weight_shape, output_shape, features)?
}
_ if op.is_activation() => {
self.lower_activation(op, input_shape, output_shape, features)?
}
MLOpKind::MaxPool2D
| MLOpKind::AvgPool2D
| MLOpKind::GlobalMaxPool2D
| MLOpKind::GlobalAvgPool2D
| MLOpKind::AdaptiveAvgPool2D => {
self.lower_pooling(op, input_shape, output_shape, features)?
}
MLOpKind::MaxPool3D => {
self.lower_pooling_3d(op, input_shape, output_shape, features)?
}
MLOpKind::BatchNorm => self.lower_batch_norm(input_shape, output_shape, features)?,
MLOpKind::LayerNorm => self.lower_layer_norm(input_shape, output_shape, features)?,
MLOpKind::GroupNorm => self.lower_group_norm(input_shape, output_shape, features)?,
MLOpKind::InstanceNorm => {
self.lower_instance_norm(input_shape, output_shape, features)?
}
MLOpKind::RMSNorm => self.lower_rms_norm(input_shape, output_shape, features)?,
MLOpKind::ScaledDotProductAttention => {
self.lower_sdpa(input_shape, output_shape, features)?
}
MLOpKind::MultiHeadAttention => self.lower_mha(input_shape, output_shape, features)?,
MLOpKind::FlashAttention => {
self.lower_flash_attention(input_shape, output_shape, features)?
}
MLOpKind::EmbeddingLookup | MLOpKind::EmbeddingBag => {
self.lower_embedding(op, input_shape, weight_shape, output_shape, features)?
}
MLOpKind::Gather | MLOpKind::GatherND => {
self.lower_gather(op, input_shape, weight_shape, output_shape, features)?
}
MLOpKind::Scatter => {
self.lower_scatter(input_shape, weight_shape, output_shape, features)?
}
MLOpKind::ReduceSum
| MLOpKind::ReduceMean
| MLOpKind::ReduceMax
| MLOpKind::ReduceMin
| MLOpKind::ReduceProd => self.lower_reduce(op, input_shape, output_shape, features)?,
MLOpKind::Add | MLOpKind::Mul | MLOpKind::Sub | MLOpKind::Div => {
self.lower_elementwise_binary(op, input_shape, output_shape, features)?
}
MLOpKind::Concat | MLOpKind::Slice | MLOpKind::Reshape | MLOpKind::Transpose => {
self.lower_data_movement(op, input_shape, output_shape, features)?
}
_ => self.lower_generic(op, input_shape, output_shape, features)?,
};
self.kernel_cache.insert((op, cache_key), result.clone());
Ok(result)
}
fn lower_gemm(
&self,
op: MLOpKind,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let (m, k) = match input_shape.rank() {
2 => (
input_shape.dim(0).unwrap_or(1),
input_shape.dim(1).unwrap_or(1),
),
3 => (
input_shape.dim(0).unwrap_or(1) * input_shape.dim(1).unwrap_or(1),
input_shape.dim(2).unwrap_or(1),
),
_ => {
return Err(MLError::UnsupportedShape(format!(
"GEMM requires 2D or 3D input, got {}",
input_shape
)))
}
};
let n = match weight_shape.rank() {
2 => weight_shape.dim(1).unwrap_or(1),
3 => weight_shape.dim(1).unwrap_or(1) * weight_shape.dim(2).unwrap_or(1),
_ => {
return Err(MLError::UnsupportedShape(
"GEMM weight must be 2D or 3D".into(),
))
}
};
let kernel = self.select_gemm_kernel(m, n, k, features);
let isa_used = kernel.as_ref().map(|k| k.requires_feature);
let kernel_name = kernel
.as_ref()
.map(|k| k.name.clone())
.unwrap_or_else(|| "x86_gemm_generic".into());
let flops = 2u64 * m as u64 * n as u64 * k as u64;
let bytes_read = (m * k + n * k) as u64 * 4; let bytes_written = (m * n) as u64 * 4;
Ok(TensorOpResult {
op,
kernel_name,
flops,
bytes_read,
bytes_written,
vectorized: kernel.is_some(),
isa_used,
block_sizes: kernel.map(|k| (k.m_block, k.n_block, k.k_block)),
})
}
fn select_gemm_kernel(
&self,
m: usize,
n: usize,
k: usize,
features: &[MLTargetFeature],
) -> Option<&GEMMMicroKernel> {
for kernel in &self.gemm_micro_kernels {
if features.contains(&kernel.requires_feature) {
if m >= kernel.m_block || n >= kernel.n_block || k >= kernel.k_block {
return Some(kernel);
}
}
}
self.gemm_micro_kernels
.iter()
.find(|k| features.contains(&k.requires_feature))
}
fn init_gemm_kernels(&mut self) {
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_sse2_m4n4k4".into(),
m_block: 4,
n_block: 4,
k_block: 4,
a_dtype: MLDataType::FP32,
b_dtype: MLDataType::FP32,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::SSE2,
flops_per_cycle: 8.0,
bytes_per_cycle: 32.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_avx2_m8n8k8".into(),
m_block: 8,
n_block: 8,
k_block: 8,
a_dtype: MLDataType::FP32,
b_dtype: MLDataType::FP32,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::AVX2,
flops_per_cycle: 16.0,
bytes_per_cycle: 64.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_avx512_m16n16k16".into(),
m_block: 16,
n_block: 16,
k_block: 16,
a_dtype: MLDataType::FP32,
b_dtype: MLDataType::FP32,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::AVX512F,
flops_per_cycle: 32.0,
bytes_per_cycle: 128.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_vnni_int8_m32n32k64".into(),
m_block: 32,
n_block: 32,
k_block: 64,
a_dtype: MLDataType::INT8,
b_dtype: MLDataType::INT8,
c_dtype: MLDataType::INT32,
requires_feature: MLTargetFeature::AVX512VNNI,
flops_per_cycle: 128.0,
bytes_per_cycle: 64.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_avx512bf16_m32n32k32".into(),
m_block: 32,
n_block: 32,
k_block: 32,
a_dtype: MLDataType::BF16,
b_dtype: MLDataType::BF16,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::AVX512BF16,
flops_per_cycle: 64.0,
bytes_per_cycle: 64.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_amx_int8_m64n64k64".into(),
m_block: 64,
n_block: 64,
k_block: 64,
a_dtype: MLDataType::INT8,
b_dtype: MLDataType::INT8,
c_dtype: MLDataType::INT32,
requires_feature: MLTargetFeature::AMXINT8,
flops_per_cycle: 256.0,
bytes_per_cycle: 128.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_amx_bf16_m32n32k32".into(),
m_block: 32,
n_block: 32,
k_block: 32,
a_dtype: MLDataType::BF16,
b_dtype: MLDataType::BF16,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::AMXBF16,
flops_per_cycle: 128.0,
bytes_per_cycle: 64.0,
});
self.gemm_micro_kernels.push(GEMMMicroKernel {
name: "x86_gemm_avx512fp16_m32n32k32".into(),
m_block: 32,
n_block: 32,
k_block: 32,
a_dtype: MLDataType::FP16,
b_dtype: MLDataType::FP16,
c_dtype: MLDataType::FP32,
requires_feature: MLTargetFeature::AVX512FP16,
flops_per_cycle: 64.0,
bytes_per_cycle: 64.0,
});
}
fn lower_conv2d(
&self,
op: MLOpKind,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let (n, c_in, h, w) = input_shape
.as_nchw()
.ok_or_else(|| MLError::UnsupportedShape("Conv2D requires NCHW input".into()))?;
let (c_out, c_in_w, kh, kw) = match weight_shape.rank() {
4 => (
weight_shape.dim(0).unwrap_or(1),
weight_shape.dim(1).unwrap_or(1),
weight_shape.dim(2).unwrap_or(1),
weight_shape.dim(3).unwrap_or(1),
),
_ => return Err(MLError::UnsupportedShape("Conv2D weight must be 4D".into())),
};
let (oh, ow) = output_shape
.as_nchw()
.map(|(_n, _c, h, w)| (h, w))
.unwrap_or((1, 1));
let flops = 2u64
* n as u64
* c_out as u64
* oh as u64
* ow as u64
* kh as u64
* kw as u64
* c_in as u64;
let (strategy, kernel_name) =
if kh == 3 && kw == 3 && features.contains(&MLTargetFeature::AVX2) {
("winograd_f2x2_3x3", "x86_conv2d_winograd_f2x2_3x3")
} else if kh <= 7 && kw <= 7 && features.contains(&MLTargetFeature::AVX512F) {
("im2col_gemm_packed", "x86_conv2d_im2col_gemm_avx512")
} else if features.contains(&MLTargetFeature::AVX2) {
("im2col_gemm", "x86_conv2d_im2col_gemm_avx2")
} else {
("direct", "x86_conv2d_direct_sse")
};
let isa = match strategy {
"winograd_f2x2_3x3" | "im2col_gemm_packed" => Some(MLTargetFeature::AVX512F),
"im2col_gemm" => Some(MLTargetFeature::AVX2),
_ => Some(MLTargetFeature::SSE2),
};
let bytes_read = (n * c_in * h * w * 4 + c_out * c_in * kh * kw * 4) as u64;
let bytes_written = (n * c_out * oh * ow * 4) as u64;
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops,
bytes_read,
bytes_written,
vectorized: strategy != "direct",
isa_used: isa,
block_sizes: Some((CONV_BLOCK_H, CONV_BLOCK_W, kh)),
})
}
fn lower_conv3d(
&self,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
if input_shape.rank() != 5 || weight_shape.rank() != 5 {
return Err(MLError::UnsupportedShape(
"Conv3D requires 5D tensors".into(),
));
}
let n = input_shape.dim(0).unwrap_or(1);
let c_in = input_shape.dim(1).unwrap_or(1);
let d = input_shape.dim(2).unwrap_or(1);
let h = input_shape.dim(3).unwrap_or(1);
let w = input_shape.dim(4).unwrap_or(1);
let c_out = weight_shape.dim(0).unwrap_or(1);
let kd = weight_shape.dim(2).unwrap_or(1);
let kh = weight_shape.dim(3).unwrap_or(1);
let kw = weight_shape.dim(4).unwrap_or(1);
let od = output_shape.dim(2).unwrap_or(1);
let oh = output_shape.dim(3).unwrap_or(1);
let ow = output_shape.dim(4).unwrap_or(1);
let flops = 2u64
* n as u64
* c_out as u64
* od as u64
* oh as u64
* ow as u64
* kd as u64
* kh as u64
* kw as u64
* c_in as u64;
let kernel_name = if features.contains(&MLTargetFeature::AVX512F) {
"x86_conv3d_im2col_gemm_avx512".into()
} else if features.contains(&MLTargetFeature::AVX2) {
"x86_conv3d_im2col_gemm_avx2".into()
} else {
"x86_conv3d_direct".into()
};
Ok(TensorOpResult {
op: MLOpKind::Conv3D,
kernel_name,
flops,
bytes_read: (n * c_in * d * h * w * 4 + c_out * c_in * kd * kh * kw * 4) as u64,
bytes_written: (n * c_out * od * oh * ow * 4) as u64,
vectorized: features.contains(&MLTargetFeature::AVX2),
isa_used: Some(if features.contains(&MLTargetFeature::AVX512F) {
MLTargetFeature::AVX512F
} else if features.contains(&MLTargetFeature::AVX2) {
MLTargetFeature::AVX2
} else {
MLTargetFeature::SSE2
}),
block_sizes: Some((4, 4, 4)),
})
}
fn lower_conv_transpose(
&self,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let (n, c_in, _h, _w) = input_shape
.as_nchw()
.ok_or_else(|| MLError::UnsupportedShape("ConvTranspose requires NCHW".into()))?;
let c_out = weight_shape.dim(0).unwrap_or(1);
let kh = weight_shape.dim(2).unwrap_or(1);
let kw = weight_shape.dim(3).unwrap_or(1);
let (oh, ow) = output_shape
.as_nchw()
.map(|(_n, _c, h, w)| (h, w))
.unwrap_or((1, 1));
let flops = 2u64
* n as u64
* c_out as u64
* oh as u64
* ow as u64
* kh as u64
* kw as u64
* c_in as u64;
Ok(TensorOpResult {
op: MLOpKind::ConvTranspose2D,
kernel_name: "x86_conv_transpose2d_col2im".into(),
flops,
bytes_read: (n
* c_in
* input_shape.dim(2).unwrap_or(1)
* input_shape.dim(3).unwrap_or(1)
* 4) as u64,
bytes_written: (n * c_out * oh * ow * 4) as u64,
vectorized: features.contains(&MLTargetFeature::AVX2),
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_activation(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let kernel_name = match op {
MLOpKind::ReLU => "x86_relu_vec",
MLOpKind::ReLU6 => "x86_relu6_vec",
MLOpKind::LeakyReLU => "x86_leaky_relu_vec",
MLOpKind::PReLU => "x86_prelu_vec",
MLOpKind::Sigmoid => {
if features.contains(&MLTargetFeature::AVX512F) {
"x86_sigmoid_avx512_lut"
} else {
"x86_sigmoid_sse_poly"
}
}
MLOpKind::Tanh => {
if features.contains(&MLTargetFeature::AVX2) {
"x86_tanh_avx2_lut"
} else {
"x86_tanh_sse_poly"
}
}
MLOpKind::GELU => "x86_gelu_avx2_approx",
MLOpKind::SiLU => "x86_silu_vec",
MLOpKind::Softmax => "x86_softmax_vec",
MLOpKind::LogSoftmax => "x86_log_softmax_vec",
MLOpKind::HardSwish => "x86_hard_swish_vec",
MLOpKind::Mish => "x86_mish_vec",
_ => return Err(MLError::UnsupportedOp(op)),
};
let flops_per_elem = match op {
MLOpKind::Sigmoid | MLOpKind::Tanh => 6, MLOpKind::GELU => 4, MLOpKind::Softmax => 4, MLOpKind::SiLU => 3, _ => 1,
};
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops: numel as u64 * flops_per_elem,
bytes_read: numel as u64 * 4,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(if features.contains(&MLTargetFeature::AVX512F) {
MLTargetFeature::AVX512F
} else if features.contains(&MLTargetFeature::AVX2) {
MLTargetFeature::AVX2
} else {
MLTargetFeature::SSE2
}),
block_sizes: None,
})
}
fn lower_pooling(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let (n, c, h, w) = input_shape.as_nchw().unwrap_or((1, 1, 1, 1));
let (oh, ow) = output_shape
.as_nchw()
.map(|(_n, _c, h, w)| (h, w))
.unwrap_or((1, 1));
let kernel_name = match op {
MLOpKind::MaxPool2D => "x86_maxpool2d_vec",
MLOpKind::AvgPool2D => "x86_avgpool2d_vec",
MLOpKind::GlobalMaxPool2D => "x86_global_maxpool_horizontal",
MLOpKind::GlobalAvgPool2D => "x86_global_avgpool_horizontal",
MLOpKind::AdaptiveAvgPool2D => "x86_adaptive_avgpool2d",
_ => return Err(MLError::UnsupportedOp(op)),
};
let flops = match op {
MLOpKind::MaxPool2D => (n * c * oh * ow * 4) as u64, MLOpKind::AvgPool2D => (n * c * oh * ow * 8) as u64, MLOpKind::GlobalMaxPool2D => (n * c * h * w) as u64,
MLOpKind::GlobalAvgPool2D => (n * c * h * w * 2) as u64,
_ => (n * c * oh * ow * 4) as u64,
};
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops,
bytes_read: (n * c * h * w * 4) as u64,
bytes_written: (n * c * oh * ow * 4) as u64,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_pooling_3d(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let n = input_shape.dim(0).unwrap_or(1);
let c = input_shape.dim(1).unwrap_or(1);
let d = input_shape.dim(2).unwrap_or(1);
let h = input_shape.dim(3).unwrap_or(1);
let w = input_shape.dim(4).unwrap_or(1);
let od = output_shape.dim(2).unwrap_or(1);
let oh = output_shape.dim(3).unwrap_or(1);
let ow = output_shape.dim(4).unwrap_or(1);
Ok(TensorOpResult {
op,
kernel_name: "x86_maxpool3d_vec".into(),
flops: (n * c * od * oh * ow * 8) as u64,
bytes_read: (n * c * d * h * w * 4) as u64,
bytes_written: (n * c * od * oh * ow * 4) as u64,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_batch_norm(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let c = input_shape.dim(1).unwrap_or(1);
Ok(TensorOpResult {
op: MLOpKind::BatchNorm,
kernel_name: "x86_batch_norm_vec".into(),
flops: (numel * 5) as u64, bytes_read: (numel * 4 + c * 4 * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_layer_norm(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let last_dim = input_shape.dims.last().copied().unwrap_or(1);
Ok(TensorOpResult {
op: MLOpKind::LayerNorm,
kernel_name: "x86_layer_norm_vec".into(),
flops: (numel * 5) as u64,
bytes_read: (numel * 4 + last_dim * 8) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_group_norm(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
Ok(TensorOpResult {
op: MLOpKind::GroupNorm,
kernel_name: "x86_group_norm_vec".into(),
flops: (numel * 5) as u64,
bytes_read: (numel * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_instance_norm(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
Ok(TensorOpResult {
op: MLOpKind::InstanceNorm,
kernel_name: "x86_instance_norm_vec".into(),
flops: (input_shape.numel() * 5) as u64,
bytes_read: (input_shape.numel() * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_rms_norm(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let last_dim = input_shape.dims.last().copied().unwrap_or(1);
Ok(TensorOpResult {
op: MLOpKind::RMSNorm,
kernel_name: "x86_rms_norm_vec".into(),
flops: (numel * 4) as u64, bytes_read: (numel * 4 + last_dim * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_sdpa(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let total_flops = if input_shape.rank() >= 4 {
let b = input_shape.dim(0).unwrap_or(1);
let h = input_shape.dim(1).unwrap_or(1);
let s = input_shape.dim(2).unwrap_or(1);
let d = input_shape.dim(3).unwrap_or(1);
(4u64 * s as u64 * s as u64 * d as u64 + 5u64 * s as u64 * s as u64)
* b as u64
* h as u64
} else {
0
};
Ok(TensorOpResult {
op: MLOpKind::ScaledDotProductAttention,
kernel_name: "x86_sdpa_tiled".into(),
flops: total_flops,
bytes_read: input_shape.numel() as u64 * 4 * 3,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: Some((64, 64, 64)),
})
}
fn lower_mha(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let total_flops = if input_shape.rank() >= 3 {
let s = input_shape.dim(1).unwrap_or(1);
let d = input_shape.dim(2).unwrap_or(1);
(3u64 * s as u64 * d as u64 * d as u64) * 2 + (s as u64 * s as u64 * d as u64 * 4) } else {
0
};
Ok(TensorOpResult {
op: MLOpKind::MultiHeadAttention,
kernel_name: "x86_mha_fused".into(),
flops: total_flops,
bytes_read: input_shape.numel() as u64 * 4 * 4,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX512F),
block_sizes: Some((128, 128, 64)),
})
}
fn lower_flash_attention(
&self,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let flops = if input_shape.rank() >= 4 {
let s = input_shape.dim(2).unwrap_or(1);
let d = input_shape.dim(3).unwrap_or(1);
4u64 * s as u64 * s as u64 * d as u64
} else {
0
};
Ok(TensorOpResult {
op: MLOpKind::FlashAttention,
kernel_name: "x86_flash_attn_tiled_online_softmax".into(),
flops,
bytes_read: input_shape.numel() as u64 * 4 * 3,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(if features.contains(&MLTargetFeature::AVX512BF16) {
MLTargetFeature::AVX512BF16
} else {
MLTargetFeature::AVX512F
}),
block_sizes: Some((64, 128, 32)),
})
}
fn lower_embedding(
&self,
op: MLOpKind,
input_shape: &TensorShape,
weight_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let indices = input_shape.numel();
let emb_dim = weight_shape.dim(1).unwrap_or(1);
Ok(TensorOpResult {
op,
kernel_name: "x86_embedding_gather".into(),
flops: 0, bytes_read: (weight_shape.numel() * 4 + indices * 4) as u64,
bytes_written: (indices * emb_dim * 4) as u64,
vectorized: false,
isa_used: None,
block_sizes: None,
})
}
fn lower_gather(
&self,
op: MLOpKind,
input_shape: &TensorShape,
index_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
Ok(TensorOpResult {
op,
kernel_name: "x86_gather".into(),
flops: 0,
bytes_read: (input_shape.numel() * 4 + index_shape.numel() * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: false,
isa_used: None,
block_sizes: None,
})
}
fn lower_scatter(
&self,
input_shape: &TensorShape,
index_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
Ok(TensorOpResult {
op: MLOpKind::Scatter,
kernel_name: "x86_scatter".into(),
flops: 0,
bytes_read: (input_shape.numel() * 4 + index_shape.numel() * 4) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: false,
isa_used: None,
block_sizes: None,
})
}
fn lower_reduce(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let kernel_name = match op {
MLOpKind::ReduceSum => "x86_reduce_sum_horizontal",
MLOpKind::ReduceMean => "x86_reduce_mean_horizontal",
MLOpKind::ReduceMax => "x86_reduce_max_horizontal",
MLOpKind::ReduceMin => "x86_reduce_min_horizontal",
MLOpKind::ReduceProd => "x86_reduce_prod_horizontal",
_ => return Err(MLError::UnsupportedOp(op)),
};
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops: (numel * 2) as u64, bytes_read: numel as u64 * 4,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: None,
})
}
fn lower_elementwise_binary(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let kernel_name = match op {
MLOpKind::Add => "x86_add_vec",
MLOpKind::Mul => "x86_mul_vec",
MLOpKind::Sub => "x86_sub_vec",
MLOpKind::Div => "x86_div_vec",
_ => return Err(MLError::UnsupportedOp(op)),
};
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops: numel as u64,
bytes_read: (numel * 4 * 2) as u64,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: true,
isa_used: Some(MLTargetFeature::SSE2),
block_sizes: None,
})
}
fn lower_data_movement(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
let numel = input_shape.numel();
let kernel_name = match op {
MLOpKind::Concat => "x86_concat_memcpy",
MLOpKind::Slice => "x86_slice_view",
MLOpKind::Reshape => "x86_reshape_view",
MLOpKind::Transpose => "x86_transpose_blocked",
_ => return Err(MLError::UnsupportedOp(op)),
};
Ok(TensorOpResult {
op,
kernel_name: kernel_name.into(),
flops: 0,
bytes_read: numel as u64 * 4,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: false,
isa_used: None,
block_sizes: None,
})
}
fn lower_generic(
&self,
op: MLOpKind,
input_shape: &TensorShape,
output_shape: &TensorShape,
features: &[MLTargetFeature],
) -> Result<TensorOpResult, MLError> {
Ok(TensorOpResult {
op,
kernel_name: "x86_generic_scalar".into(),
flops: input_shape.numel() as u64,
bytes_read: input_shape.numel() as u64 * 4,
bytes_written: output_shape.numel() as u64 * 4,
vectorized: false,
isa_used: None,
block_sizes: None,
})
}
fn init_winograd_configs(&mut self) {
self.winograd_configs.insert(
(2, 3),
WinogradConfig {
f_m: 2,
f_r: 3,
tile_size: 4,
transform_a: vec![
1.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
-1.0,
],
transform_b: vec![1.0, 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.0, 0.0, 1.0],
transform_c: vec![1.0, 1.0, 1.0, 0.0, 0.0, 1.0, -1.0, -1.0],
},
);
self.winograd_configs.insert(
(4, 3),
WinogradConfig {
f_m: 4,
f_r: 3,
tile_size: 6,
transform_a: vec![
1.0,
0.0,
-2.5,
0.0,
1.0,
0.0,
0.0,
-1.0,
1.5,
1.5,
-1.0,
0.0,
0.0,
0.5,
-2.0,
2.0,
-0.5,
0.0,
0.0,
-1.0 / 6.0,
2.0 / 3.0,
-2.0 / 3.0,
1.0 / 6.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
-1.0,
],
transform_b: vec![
1.0,
0.0,
0.0,
-2.0 / 3.0,
-2.0 / 3.0,
-2.0 / 3.0,
-2.0 / 3.0,
2.0 / 3.0,
-2.0 / 3.0,
1.0 / 6.0,
1.0 / 3.0,
2.0 / 3.0,
1.0 / 6.0,
-1.0 / 3.0,
2.0 / 3.0,
0.0,
0.0,
1.0,
],
transform_c: vec![
1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, -1.0, 2.0, -2.0, 0.0, 0.0, 1.0, 1.0,
4.0, 4.0, 0.0, 0.0, 1.0, -1.0, 8.0, -8.0, 1.0,
],
},
);
}
fn shape_signature(
&self,
op: MLOpKind,
input: &TensorShape,
weight: &TensorShape,
output: &TensorShape,
) -> String {
format!("{}:{}|{}|{}", op.name(), input, weight, output)
}
pub fn record_tuning(&mut self, entry: TuningEntry) {
self.tuning_history.push(entry);
}
pub fn best_kernel(&self, op: MLOpKind, shape_sig: &str) -> Option<&TuningEntry> {
self.tuning_history
.iter()
.filter(|e| e.op == op && e.shape_signature == shape_sig)
.min_by(|a, b| {
a.runtime_us
.partial_cmp(&b.runtime_us)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn gemm_kernels(&self) -> &[GEMMMicroKernel] {
&self.gemm_micro_kernels
}
pub fn winograd_config(&self, f_m: usize, f_r: usize) -> Option<&WinogradConfig> {
self.winograd_configs.get(&(f_m, f_r))
}
}
pub struct X86VectorML {
pub max_feature: MLTargetFeature,
pub kernels: Vec<VectorizedKernel>,
pub vnni_config: VNNIConfig,
pub bf16_config: BF16Config,
pub fp16_config: FP16Config,
pub int8_config: INT8Config,
pub amx_config: AMXConfig,
}
#[derive(Debug, Clone)]
pub struct VectorizedKernel {
pub name: String,
pub op: MLOpKind,
pub dtype: MLDataType,
pub isa: MLTargetFeature,
pub vector_width_bytes: usize,
pub lanes: usize,
pub instructions: Vec<VectorInstr>,
pub flops_per_call: u64,
}
#[derive(Debug, Clone)]
pub enum VectorInstr {
Load {
dst: String,
src: String,
aligned: bool,
},
Store {
src: String,
dst: String,
aligned: bool,
},
FMAdd {
dst: String,
a: String,
b: String,
c: String,
},
FMSub {
dst: String,
a: String,
b: String,
c: String,
},
Mul {
dst: String,
a: String,
b: String,
},
Add {
dst: String,
a: String,
b: String,
},
Sub {
dst: String,
a: String,
b: String,
},
Div {
dst: String,
a: String,
b: String,
},
Max {
dst: String,
a: String,
b: String,
},
Min {
dst: String,
a: String,
b: String,
},
Cmp {
dst: String,
a: String,
b: String,
pred: String,
},
Blend {
dst: String,
a: String,
b: String,
mask: String,
},
Shuffle {
dst: String,
a: String,
b: String,
imm: u8,
},
Permute {
dst: String,
src: String,
indices: String,
},
Broadcast {
dst: String,
src: String,
},
Reduce {
dst: String,
src: String,
op: String,
},
CVT {
dst: String,
src: String,
from: MLDataType,
to: MLDataType,
},
VNNIDot {
dst: String,
a: String,
b: String,
},
TileLoad {
dst: String,
src: String,
},
TileStore {
src: String,
dst: String,
},
TileDP {
dst: String,
a: String,
b: String,
kind: String,
},
TileZero {
dst: String,
},
Comment(String),
}
#[derive(Debug, Clone)]
pub struct VNNIConfig {
pub enabled: bool,
pub instructions: Vec<String>,
pub input_dtype: MLDataType,
pub weight_dtype: MLDataType,
pub accum_dtype: MLDataType,
pub elements_per_inst: usize,
}
impl Default for VNNIConfig {
fn default() -> Self {
Self {
enabled: false,
instructions: vec![
"vpdpbusd".into(),
"vpdpbusds".into(),
"vpdpwssd".into(),
"vpdpwssds".into(),
],
input_dtype: MLDataType::UINT8,
weight_dtype: MLDataType::INT8,
accum_dtype: MLDataType::INT32,
elements_per_inst: 4,
}
}
}
#[derive(Debug, Clone)]
pub struct BF16Config {
pub enabled: bool,
pub cvt_instr: String,
pub dp_instr: String,
pub lanes: usize,
}
impl Default for BF16Config {
fn default() -> Self {
Self {
enabled: false,
cvt_instr: "vcvtne2ps2bf16".into(),
dp_instr: "vdpbf16ps".into(),
lanes: 32,
}
}
}
#[derive(Debug, Clone)]
pub struct FP16Config {
pub enabled: bool,
pub fmadd_instr: String,
pub mul_instr: String,
pub cvt_instr: String,
pub lanes: usize,
}
impl Default for FP16Config {
fn default() -> Self {
Self {
enabled: false,
fmadd_instr: "vfmadd132ph".into(),
mul_instr: "vmulph".into(),
cvt_instr: "vcvtph2ps".into(),
lanes: 32,
}
}
}
#[derive(Debug, Clone)]
pub struct INT8Config {
pub enabled: bool,
pub dp_instrs: Vec<String>,
pub lanes: usize,
pub scale_factor: f32,
}
impl Default for INT8Config {
fn default() -> Self {
Self {
enabled: false,
dp_instrs: vec![
"vpdpbssd".into(),
"vpdpbsud".into(),
"vpdpbusd".into(),
"vpdpbuud".into(),
],
lanes: 16,
scale_factor: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct AMXConfig {
pub enabled: bool,
pub num_tiles: usize,
pub max_rows: usize,
pub max_col_bytes: usize,
pub tile_load_instr: String,
pub tile_store_instr: String,
pub tile_dp_int8_instr: String,
pub tile_dp_bf16_instr: String,
pub tile_dp_fp16_instr: String,
pub tile_zero_instr: String,
pub tile_release_instr: String,
}
impl Default for AMXConfig {
fn default() -> Self {
Self {
enabled: false,
num_tiles: 8,
max_rows: 16,
max_col_bytes: 64,
tile_load_instr: "tileloadd".into(),
tile_store_instr: "tilestored".into(),
tile_dp_int8_instr: "tdpbssd".into(),
tile_dp_bf16_instr: "tdpbf16ps".into(),
tile_dp_fp16_instr: "tdpfp16ps".into(),
tile_zero_instr: "tilezero".into(),
tile_release_instr: "tilerelease".into(),
}
}
}
impl X86VectorML {
pub fn new(max_feature: MLTargetFeature) -> Self {
let mut ml = Self {
max_feature,
kernels: Vec::new(),
vnni_config: VNNIConfig::default(),
bf16_config: BF16Config::default(),
fp16_config: FP16Config::default(),
int8_config: INT8Config::default(),
amx_config: AMXConfig::default(),
};
ml.configure_from_feature(max_feature);
ml
}
fn configure_from_feature(&mut self, feature: MLTargetFeature) {
match feature {
MLTargetFeature::AVX512VNNI => {
self.vnni_config.enabled = true;
self.int8_config.enabled = true;
}
MLTargetFeature::AVX512BF16 => {
self.bf16_config.enabled = true;
}
MLTargetFeature::AVX512FP16 => {
self.fp16_config.enabled = true;
}
MLTargetFeature::AMXINT8 => {
self.amx_config.enabled = true;
self.vnni_config.enabled = true;
self.int8_config.enabled = true;
}
MLTargetFeature::AMXBF16 => {
self.amx_config.enabled = true;
self.bf16_config.enabled = true;
}
MLTargetFeature::AMXFP16 => {
self.amx_config.enabled = true;
self.fp16_config.enabled = true;
}
_ => {}
}
}
pub fn vectorize(
&mut self,
op: MLOpKind,
dtype: MLDataType,
features: &[MLTargetFeature],
) -> Result<Vec<VectorizedKernel>, MLError> {
let isa = self.best_isa_for_op(op, dtype, features)?;
let kernel = self.generate_kernel(op, dtype, isa)?;
self.kernels.push(kernel.clone());
Ok(vec![kernel])
}
pub fn best_isa_for_op(
&self,
_op: MLOpKind,
dtype: MLDataType,
features: &[MLTargetFeature],
) -> Result<MLTargetFeature, MLError> {
match dtype {
MLDataType::INT8 | MLDataType::UINT8 => {
if features.contains(&MLTargetFeature::AMXINT8) {
Ok(MLTargetFeature::AMXINT8)
} else if features.contains(&MLTargetFeature::AVX512VNNI) {
Ok(MLTargetFeature::AVX512VNNI)
} else {
Err(MLError::FeatureNotAvailable(MLTargetFeature::AVX512VNNI))
}
}
MLDataType::BF16 => {
if features.contains(&MLTargetFeature::AMXBF16) {
Ok(MLTargetFeature::AMXBF16)
} else if features.contains(&MLTargetFeature::AVX512BF16) {
Ok(MLTargetFeature::AVX512BF16)
} else {
Err(MLError::FeatureNotAvailable(MLTargetFeature::AVX512BF16))
}
}
MLDataType::FP16 => {
if features.contains(&MLTargetFeature::AMXFP16) {
Ok(MLTargetFeature::AMXFP16)
} else if features.contains(&MLTargetFeature::AVX512FP16) {
Ok(MLTargetFeature::AVX512FP16)
} else {
Err(MLError::FeatureNotAvailable(MLTargetFeature::AVX512FP16))
}
}
MLDataType::FP32 => {
if features.contains(&MLTargetFeature::AVX512F) {
Ok(MLTargetFeature::AVX512F)
} else if features.contains(&MLTargetFeature::AVX2) {
Ok(MLTargetFeature::AVX2)
} else if features.contains(&MLTargetFeature::AVX) {
Ok(MLTargetFeature::AVX)
} else {
Ok(MLTargetFeature::SSE2)
}
}
_ => Ok(MLTargetFeature::SSE2),
}
}
pub fn generate_kernel(
&self,
op: MLOpKind,
dtype: MLDataType,
isa: MLTargetFeature,
) -> Result<VectorizedKernel, MLError> {
let (vector_width, lanes) = self.vector_width(isa, dtype);
let instructions = match op {
MLOpKind::MatMul => self.gen_gemm_instrs(dtype, isa),
MLOpKind::Conv2D => self.gen_conv2d_instrs(dtype, isa),
MLOpKind::ReLU => self.gen_relu_instrs(dtype, isa),
MLOpKind::GELU => self.gen_gelu_instrs(dtype, isa),
MLOpKind::SiLU => self.gen_silu_instrs(dtype, isa),
MLOpKind::Softmax => self.gen_softmax_instrs(dtype, isa),
MLOpKind::LayerNorm => self.gen_layernorm_instrs(dtype, isa),
MLOpKind::RMSNorm => self.gen_rmsnorm_instrs(dtype, isa),
MLOpKind::Add => self.gen_ew_instrs("add", dtype, isa),
MLOpKind::Mul => self.gen_ew_instrs("mul", dtype, isa),
_ => self.gen_generic_instrs(op, dtype, isa),
};
Ok(VectorizedKernel {
name: format!(
"x86_vec_{}_{:?}_{}",
op.name().to_lowercase(),
dtype,
isa.isa_level()
),
op,
dtype,
isa,
vector_width_bytes: vector_width,
lanes,
instructions,
flops_per_call: lanes as u64,
})
}
pub fn vector_width(&self, isa: MLTargetFeature, dtype: MLDataType) -> (usize, usize) {
let bytes = match isa {
MLTargetFeature::AMXINT8 | MLTargetFeature::AMXBF16 | MLTargetFeature::AMXFP16 => 1024,
MLTargetFeature::AVX512VNNI
| MLTargetFeature::AVX512BF16
| MLTargetFeature::AVX512FP16
| MLTargetFeature::AVX512F => 64,
MLTargetFeature::AVX2 | MLTargetFeature::AVX => 32,
_ => 16,
};
let lanes = bytes * 8 / dtype.elem_bits();
(bytes, lanes)
}
fn gen_gemm_instrs(&self, dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let mut ins = Vec::new();
ins.push(VectorInstr::Comment(format!("GEMM: {:?} {:?}", dtype, isa)));
match (dtype, isa) {
(MLDataType::INT8, MLTargetFeature::AVX512VNNI) => {
ins.push(VectorInstr::Load {
dst: "%zmm0".into(),
src: "[%rsi]".into(),
aligned: true,
});
ins.push(VectorInstr::Load {
dst: "%zmm1".into(),
src: "[%rdx]".into(),
aligned: true,
});
ins.push(VectorInstr::VNNIDot {
dst: "%zmm2".into(),
a: "%zmm0".into(),
b: "%zmm1".into(),
});
ins.push(VectorInstr::Store {
src: "%zmm2".into(),
dst: "[%rdi]".into(),
aligned: true,
});
}
(MLDataType::BF16, MLTargetFeature::AVX512BF16) => {
ins.push(VectorInstr::Load {
dst: "%zmm0".into(),
src: "[%rsi]".into(),
aligned: true,
});
ins.push(VectorInstr::Load {
dst: "%zmm1".into(),
src: "[%rdx]".into(),
aligned: true,
});
ins.push(VectorInstr::FMAdd {
dst: "%zmm2".into(),
a: "%zmm0".into(),
b: "%zmm1".into(),
c: "%zmm2".into(),
});
ins.push(VectorInstr::Store {
src: "%zmm2".into(),
dst: "[%rdi]".into(),
aligned: true,
});
}
(MLDataType::FP16, MLTargetFeature::AVX512FP16) => {
ins.push(VectorInstr::Load {
dst: "%zmm0".into(),
src: "[%rsi]".into(),
aligned: true,
});
ins.push(VectorInstr::Load {
dst: "%zmm1".into(),
src: "[%rdx]".into(),
aligned: true,
});
ins.push(VectorInstr::FMAdd {
dst: "%zmm2".into(),
a: "%zmm0".into(),
b: "%zmm1".into(),
c: "%zmm2".into(),
});
ins.push(VectorInstr::Store {
src: "%zmm2".into(),
dst: "[%rdi]".into(),
aligned: true,
});
}
(_, MLTargetFeature::AMXINT8) => {
ins.push(VectorInstr::TileLoad {
dst: "tmm0".into(),
src: "[%rsi]".into(),
});
ins.push(VectorInstr::TileLoad {
dst: "tmm1".into(),
src: "[%rdx]".into(),
});
ins.push(VectorInstr::TileDP {
dst: "tmm2".into(),
a: "tmm0".into(),
b: "tmm1".into(),
kind: "int8".into(),
});
ins.push(VectorInstr::TileStore {
src: "tmm2".into(),
dst: "[%rdi]".into(),
});
}
(_, MLTargetFeature::AMXBF16) => {
ins.push(VectorInstr::TileLoad {
dst: "tmm0".into(),
src: "[%rsi]".into(),
});
ins.push(VectorInstr::TileLoad {
dst: "tmm1".into(),
src: "[%rdx]".into(),
});
ins.push(VectorInstr::TileDP {
dst: "tmm2".into(),
a: "tmm0".into(),
b: "tmm1".into(),
kind: "bf16".into(),
});
ins.push(VectorInstr::TileStore {
src: "tmm2".into(),
dst: "[%rdi]".into(),
});
}
_ => {
let reg = self.reg_prefix(isa);
ins.push(VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
});
ins.push(VectorInstr::Load {
dst: format!("%{}1", reg),
src: "[%rdx]".into(),
aligned: true,
});
ins.push(VectorInstr::FMAdd {
dst: format!("%{}2", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
c: format!("%{}2", reg),
});
ins.push(VectorInstr::Store {
src: format!("%{}2", reg),
dst: "[%rdi]".into(),
aligned: true,
});
}
}
ins
}
fn gen_conv2d_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("Conv2D im2col+GEMM".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi+offset]".into(),
aligned: false,
},
VectorInstr::Load {
dst: format!("%{}1", reg),
src: "[%rdx+offset]".into(),
aligned: true,
},
VectorInstr::FMAdd {
dst: format!("%{}2", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
c: format!("%{}2", reg),
},
VectorInstr::Store {
src: format!("%{}2", reg),
dst: "[%rdi+offset]".into(),
aligned: true,
},
]
}
fn gen_relu_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("ReLU: max(x, 0)".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Max {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
},
VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_gelu_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("GELU: x * Phi(x)".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Broadcast {
dst: format!("%{}1", reg),
src: "0.044715".into(),
},
VectorInstr::Broadcast {
dst: format!("%{}2", reg),
src: "0.79788456".into(),
},
VectorInstr::Mul {
dst: format!("%{}3", reg),
a: format!("%{}0", reg),
b: format!("%{}0", reg),
},
VectorInstr::Mul {
dst: format!("%{}3", reg),
a: format!("%{}3", reg),
b: format!("%{}0", reg),
},
VectorInstr::FMAdd {
dst: format!("%{}4", reg),
a: format!("%{}1", reg),
b: format!("%{}3", reg),
c: format!("%{}0", reg),
},
VectorInstr::Mul {
dst: format!("%{}4", reg),
a: format!("%{}2", reg),
b: format!("%{}4", reg),
},
VectorInstr::Mul {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}4", reg),
},
VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_silu_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("SiLU: x * sigmoid(x)".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Broadcast {
dst: format!("%{}1", reg),
src: "1.0".into(),
},
VectorInstr::Broadcast {
dst: format!("%{}2", reg),
src: "-1.0".into(),
},
VectorInstr::Mul {
dst: format!("%{}3", reg),
a: format!("%{}0", reg),
b: format!("%{}2", reg),
},
VectorInstr::FMAdd {
dst: format!("%{}1", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
c: format!("%{}1", reg),
},
VectorInstr::Store {
src: format!("%{}1", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_softmax_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("Softmax: exp(x-max)/sum(exp)".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Reduce {
dst: format!("%{}1", reg),
src: format!("%{}0", reg),
op: "max".into(),
},
VectorInstr::Sub {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
},
VectorInstr::Reduce {
dst: format!("%{}2", reg),
src: format!("%{}0", reg),
op: "sum".into(),
},
VectorInstr::Div {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}2", reg),
},
VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_layernorm_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("LayerNorm: (x-mean)/sqrt(var+eps)*gamma+beta".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Reduce {
dst: format!("%{}1", reg),
src: format!("%{}0", reg),
op: "sum".into(),
},
VectorInstr::Sub {
dst: format!("%{}2", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
},
VectorInstr::Mul {
dst: format!("%{}3", reg),
a: format!("%{}2", reg),
b: format!("%{}2", reg),
},
VectorInstr::Reduce {
dst: format!("%{}4", reg),
src: format!("%{}3", reg),
op: "sum".into(),
},
VectorInstr::Broadcast {
dst: format!("%{}5", reg),
src: "eps".into(),
},
VectorInstr::Add {
dst: format!("%{}4", reg),
a: format!("%{}4", reg),
b: format!("%{}5", reg),
},
VectorInstr::Div {
dst: format!("%{}2", reg),
a: format!("%{}2", reg),
b: format!("%{}4", reg),
},
VectorInstr::Load {
dst: format!("%{}6", reg),
src: "[%rdx]".into(),
aligned: true,
},
VectorInstr::Load {
dst: format!("%{}7", reg),
src: "[%rcx]".into(),
aligned: true,
},
VectorInstr::FMAdd {
dst: format!("%{}2", reg),
a: format!("%{}2", reg),
b: format!("%{}6", reg),
c: format!("%{}7", reg),
},
VectorInstr::Store {
src: format!("%{}2", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_rmsnorm_instrs(&self, _dtype: MLDataType, isa: MLTargetFeature) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment("RMSNorm: x/rms(x)*gamma".into()),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Mul {
dst: format!("%{}1", reg),
a: format!("%{}0", reg),
b: format!("%{}0", reg),
},
VectorInstr::Reduce {
dst: format!("%{}2", reg),
src: format!("%{}1", reg),
op: "sum".into(),
},
VectorInstr::Broadcast {
dst: format!("%{}3", reg),
src: "eps".into(),
},
VectorInstr::Add {
dst: format!("%{}2", reg),
a: format!("%{}2", reg),
b: format!("%{}3", reg),
},
VectorInstr::Div {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}2", reg),
},
VectorInstr::Load {
dst: format!("%{}4", reg),
src: "[%rdx]".into(),
aligned: true,
},
VectorInstr::Mul {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}4", reg),
},
VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn gen_ew_instrs(
&self,
op_name: &str,
_dtype: MLDataType,
isa: MLTargetFeature,
) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
let mut ins = vec![
VectorInstr::Comment(format!("Element-wise {}", op_name)),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Load {
dst: format!("%{}1", reg),
src: "[%rdx]".into(),
aligned: true,
},
];
match op_name {
"add" => ins.push(VectorInstr::Add {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
}),
"mul" => ins.push(VectorInstr::Mul {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
}),
_ => ins.push(VectorInstr::Add {
dst: format!("%{}0", reg),
a: format!("%{}0", reg),
b: format!("%{}1", reg),
}),
}
ins.push(VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
});
ins
}
fn gen_generic_instrs(
&self,
op: MLOpKind,
_dtype: MLDataType,
isa: MLTargetFeature,
) -> Vec<VectorInstr> {
let reg = self.reg_prefix(isa);
vec![
VectorInstr::Comment(format!("Generic: {}", op.name())),
VectorInstr::Load {
dst: format!("%{}0", reg),
src: "[%rsi]".into(),
aligned: true,
},
VectorInstr::Store {
src: format!("%{}0", reg),
dst: "[%rdi]".into(),
aligned: true,
},
]
}
fn reg_prefix(&self, isa: MLTargetFeature) -> &'static str {
match isa {
MLTargetFeature::AVX512VNNI
| MLTargetFeature::AVX512BF16
| MLTargetFeature::AVX512FP16
| MLTargetFeature::AVX512F => "zmm",
MLTargetFeature::AVX2 | MLTargetFeature::AVX => "ymm",
_ => "xmm",
}
}
pub fn has_vnni(&self) -> bool {
self.vnni_config.enabled
}
pub fn has_bf16(&self) -> bool {
self.bf16_config.enabled
}
pub fn has_fp16(&self) -> bool {
self.fp16_config.enabled
}
pub fn has_amx(&self) -> bool {
self.amx_config.enabled
}
pub fn all_kernels(&self) -> &[VectorizedKernel] {
&self.kernels
}
pub fn emit_gemm_asm(&self, m: usize, n: usize, k: usize, dtype: MLDataType) -> String {
let isa = self.max_feature;
let reg = self.reg_prefix(isa);
match (dtype, isa) {
(MLDataType::INT8, MLTargetFeature::AVX512VNNI) => format!(
"# GEMM INT8 VNNI: M={m}, N={n}, K={k}\n\
.L_gemm_vnni:\n\
\tvmovdqu32 ({reg}mm0), [%rsi+%rax]\n\
\tvmovdqu32 ({reg}mm1), [%rdx+%rbx]\n\
\tvpdpbusd {reg}mm2, {reg}mm0, {reg}mm1\n\
\tvmovdqu32 [%rdi+%rcx], ({reg}mm2)\n\
\tadd $64, %rax\n\tadd $64, %rbx\n\tjl .L_gemm_vnni\n"
),
(MLDataType::BF16, MLTargetFeature::AVX512BF16) => format!(
"# GEMM BF16: M={m}, N={n}, K={k}\n\
.L_gemm_bf16:\n\
\tvmovdqu16 ({reg}mm0), [%rsi+%rax]\n\
\tvmovdqu16 ({reg}mm1), [%rdx+%rbx]\n\
\tvdpbf16ps {reg}mm2, {reg}mm0, {reg}mm1\n\
\tvmovups [%rdi+%rcx], ({reg}mm2)\n\
\tadd $64, %rax\n\tjl .L_gemm_bf16\n"
),
(MLDataType::FP16, MLTargetFeature::AVX512FP16) => format!(
"# GEMM FP16: M={m}, N={n}, K={k}\n\
.L_gemm_fp16:\n\
\tvmovdqu16 ({reg}mm0), [%rsi+%rax]\n\
\tvmovdqu16 ({reg}mm1), [%rdx+%rbx]\n\
\tvfmadd132ph {reg}mm1, {reg}mm2, {reg}mm0\n\
\tvmovdqu16 [%rdi+%rcx], ({reg}mm2)\n\
\tadd $64, %rax\n\tjl .L_gemm_fp16\n"
),
_ => format!(
"# GEMM FP32: M={m}, N={n}, K={k}\n\
.L_gemm_fp32:\n\
\tvmovups ({reg}mm0), [%rsi+%rax]\n\
\tvmovups ({reg}mm1), [%rdx+%rbx]\n\
\tvfmadd231ps {reg}mm2, {reg}mm0, {reg}mm1\n\
\tadd $64, %rax\n\tjl .L_gemm_fp32\n\
\tvmovups [%rdi+%rcx], ({reg}mm2)\n"
),
}
}
pub fn emit_amx_config_asm(&self, rows: u8, cols_bytes: u16) -> String {
format!(
"# AMX Tile Config: {rows} rows × {cols_bytes} bytes/row\n\
.L_amx_config:\n\
\tmovb ${rows}, [%rsp-64]\n\
\tmovw ${cols_bytes}, [%rsp-63]\n\
\tldtilecfg [%rsp-64]\n"
)
}
pub fn emit_amx_gemm_asm(&self, m: usize, n: usize, k: usize, dtype: &str) -> String {
let dp_instr = match dtype {
"int8" => "tdpbssd",
"bf16" => "tdpbf16ps",
"fp16" => "tdpfp16ps",
_ => "tdpbssd",
};
format!(
"# AMX GEMM {dtype}: M={m}, N={n}, K={k}\n\
.L_amx_gemm:\n\
\ttileloadd tmm0, [%rsi+%rax]\n\
\ttileloadd tmm1, [%rdx+%rbx]\n\
\t{dp_instr} tmm2, tmm0, tmm1\n\
\tadd $1024, %rax\n\
\tjl .L_amx_gemm\n\
\ttilestored [%rdi+%rcx], tmm2\n"
)
}
}
pub struct X86MLIntrinsics {
pub max_feature: MLTargetFeature,
intrinsics_table: HashMap<String, IntrinsicDescriptor>,
}
#[derive(Debug, Clone)]
pub struct IntrinsicDescriptor {
pub builtin_name: String,
pub instr_mnemonic: String,
pub category: IntrinsicCategory,
pub required_feature: MLTargetFeature,
pub operands: Vec<IntrinsicOperand>,
pub result_type: MLDataType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntrinsicCategory {
VNNI,
BF16,
FP16,
INT8Dot,
AMXTile,
AMXDot,
Conversion,
Activation,
}
#[derive(Debug, Clone)]
pub struct IntrinsicOperand {
pub name: String,
pub dtype: MLDataType,
pub is_vector: bool,
pub vector_width: usize,
}
impl X86MLIntrinsics {
pub fn new(max_feature: MLTargetFeature) -> Self {
let mut s = Self {
max_feature,
intrinsics_table: HashMap::new(),
};
s.register_intrinsics();
s
}
fn register_intrinsics(&mut self) {
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbusd".into(),
instr_mnemonic: "vpdpbusd".into(),
category: IntrinsicCategory::VNNI,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbusds".into(),
instr_mnemonic: "vpdpbusds".into(),
category: IntrinsicCategory::VNNI,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpwssd".into(),
instr_mnemonic: "vpdpwssd".into(),
category: IntrinsicCategory::VNNI,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::INT16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpwssds".into(),
instr_mnemonic: "vpdpwssds".into(),
category: IntrinsicCategory::VNNI,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::INT16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vcvtne2ps2bf16".into(),
instr_mnemonic: "vcvtne2ps2bf16".into(),
category: IntrinsicCategory::BF16,
required_feature: MLTargetFeature::AVX512BF16,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::FP32,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::FP32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::BF16,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vdpbf16ps".into(),
instr_mnemonic: "vdpbf16ps".into(),
category: IntrinsicCategory::BF16,
required_feature: MLTargetFeature::AVX512BF16,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::BF16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::BF16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::FP32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::FP32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vfmadd132ph".into(),
instr_mnemonic: "vfmadd132ph".into(),
category: IntrinsicCategory::FP16,
required_feature: MLTargetFeature::AVX512FP16,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::FP16,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vfmsub132ph".into(),
instr_mnemonic: "vfmsub132ph".into(),
category: IntrinsicCategory::FP16,
required_feature: MLTargetFeature::AVX512FP16,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::FP16,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vcvtph2ps".into(),
instr_mnemonic: "vcvtph2ps".into(),
category: IntrinsicCategory::Conversion,
required_feature: MLTargetFeature::AVX512FP16,
operands: vec![IntrinsicOperand {
name: "src".into(),
dtype: MLDataType::FP16,
is_vector: true,
vector_width: 256,
}],
result_type: MLDataType::FP32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vcvtps2ph".into(),
instr_mnemonic: "vcvtps2ph".into(),
category: IntrinsicCategory::Conversion,
required_feature: MLTargetFeature::AVX512FP16,
operands: vec![IntrinsicOperand {
name: "src".into(),
dtype: MLDataType::FP32,
is_vector: true,
vector_width: 512,
}],
result_type: MLDataType::FP16,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbssd".into(),
instr_mnemonic: "vpdpbssd".into(),
category: IntrinsicCategory::INT8Dot,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbsud".into(),
instr_mnemonic: "vpdpbsud".into(),
category: IntrinsicCategory::INT8Dot,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbusd".into(),
instr_mnemonic: "vpdpbusd".into(),
category: IntrinsicCategory::INT8Dot,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::INT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_vpdpbuud".into(),
instr_mnemonic: "vpdpbuud".into(),
category: IntrinsicCategory::INT8Dot,
required_feature: MLTargetFeature::AVX512VNNI,
operands: vec![
IntrinsicOperand {
name: "src1".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src2".into(),
dtype: MLDataType::UINT8,
is_vector: true,
vector_width: 512,
},
IntrinsicOperand {
name: "src3".into(),
dtype: MLDataType::INT32,
is_vector: true,
vector_width: 512,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tileloadd64".into(),
instr_mnemonic: "tileloadd".into(),
category: IntrinsicCategory::AMXTile,
required_feature: MLTargetFeature::AMXINT8,
operands: vec![
IntrinsicOperand {
name: "tile".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "mem".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
],
result_type: MLDataType::INT8,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tilestored64".into(),
instr_mnemonic: "tilestored".into(),
category: IntrinsicCategory::AMXTile,
required_feature: MLTargetFeature::AMXINT8,
operands: vec![
IntrinsicOperand {
name: "mem".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
],
result_type: MLDataType::INT8,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tdpbssd".into(),
instr_mnemonic: "tdpbssd".into(),
category: IntrinsicCategory::AMXDot,
required_feature: MLTargetFeature::AMXINT8,
operands: vec![
IntrinsicOperand {
name: "tile_dst".into(),
dtype: MLDataType::INT32,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_a".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_b".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
},
],
result_type: MLDataType::INT32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tdpbf16ps".into(),
instr_mnemonic: "tdpbf16ps".into(),
category: IntrinsicCategory::AMXDot,
required_feature: MLTargetFeature::AMXBF16,
operands: vec![
IntrinsicOperand {
name: "tile_dst".into(),
dtype: MLDataType::FP32,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_a".into(),
dtype: MLDataType::BF16,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_b".into(),
dtype: MLDataType::BF16,
is_vector: false,
vector_width: 0,
},
],
result_type: MLDataType::FP32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tdpfp16ps".into(),
instr_mnemonic: "tdpfp16ps".into(),
category: IntrinsicCategory::AMXDot,
required_feature: MLTargetFeature::AMXFP16,
operands: vec![
IntrinsicOperand {
name: "tile_dst".into(),
dtype: MLDataType::FP32,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_a".into(),
dtype: MLDataType::FP16,
is_vector: false,
vector_width: 0,
},
IntrinsicOperand {
name: "tile_b".into(),
dtype: MLDataType::FP16,
is_vector: false,
vector_width: 0,
},
],
result_type: MLDataType::FP32,
});
self.register(IntrinsicDescriptor {
builtin_name: "__builtin_ia32_tilezero".into(),
instr_mnemonic: "tilezero".into(),
category: IntrinsicCategory::AMXTile,
required_feature: MLTargetFeature::AMXINT8,
operands: vec![IntrinsicOperand {
name: "tile".into(),
dtype: MLDataType::INT8,
is_vector: false,
vector_width: 0,
}],
result_type: MLDataType::INT8,
});
}
fn register(&mut self, desc: IntrinsicDescriptor) {
self.intrinsics_table
.insert(desc.builtin_name.clone(), desc);
}
pub fn lookup(&self, builtin_name: &str) -> Option<&IntrinsicDescriptor> {
self.intrinsics_table.get(builtin_name)
}
pub fn is_available(&self, builtin_name: &str, features: &[MLTargetFeature]) -> bool {
if let Some(desc) = self.lookup(builtin_name) {
features.contains(&desc.required_feature)
} else {
false
}
}
pub fn intrinsics_by_category(&self, category: IntrinsicCategory) -> Vec<&IntrinsicDescriptor> {
self.intrinsics_table
.values()
.filter(|d| d.category == category)
.collect()
}
pub fn vnni_int8_intrinsics(&self) -> Vec<&IntrinsicDescriptor> {
self.intrinsics_by_category(IntrinsicCategory::VNNI)
}
pub fn bf16_intrinsics(&self) -> Vec<&IntrinsicDescriptor> {
self.intrinsics_by_category(IntrinsicCategory::BF16)
}
pub fn fp16_intrinsics(&self) -> Vec<&IntrinsicDescriptor> {
self.intrinsics_by_category(IntrinsicCategory::FP16)
}
pub fn amx_intrinsics(&self) -> Vec<&IntrinsicDescriptor> {
self.intrinsics_table
.values()
.filter(|d| {
d.category == IntrinsicCategory::AMXTile || d.category == IntrinsicCategory::AMXDot
})
.collect()
}
pub fn emit_decl(&self, desc: &IntrinsicDescriptor) -> String {
let result = desc.result_type;
let mut params = String::new();
for (i, op) in desc.operands.iter().enumerate() {
if i > 0 {
params.push_str(", ");
}
if op.is_vector {
params.push_str(&format!("__m512i /* {} */", op.name));
} else {
params.push_str(&format!("void* /* {} */", op.name));
}
}
format!(
"extern __m512i {} ({}); // {} (requires {})",
desc.builtin_name,
params,
desc.instr_mnemonic,
desc.required_feature.isa_level()
)
}
pub fn emit_header(&self) -> String {
let mut out = String::from(
"/* Auto-generated X86 ML Intrinsics Header */\n\
#ifndef _X86_ML_INTRINSICS_H_\n\
#define _X86_ML_INTRINSICS_H_\n\n\
#include <immintrin.h>\n\n",
);
out.push_str("/* === VNNI Intrinsics === */\n");
for d in self.vnni_int8_intrinsics() {
out.push_str(&self.emit_decl(d));
out.push('\n');
}
out.push_str("\n/* === BF16 Intrinsics === */\n");
for d in self.bf16_intrinsics() {
out.push_str(&self.emit_decl(d));
out.push('\n');
}
out.push_str("\n/* === FP16 Intrinsics === */\n");
for d in self.fp16_intrinsics() {
out.push_str(&self.emit_decl(d));
out.push('\n');
}
out.push_str("\n/* === AMX Intrinsics === */\n");
for d in self.amx_intrinsics() {
out.push_str(&self.emit_decl(d));
out.push('\n');
}
out.push_str("\n#endif /* _X86_ML_INTRINSICS_H_ */\n");
out
}
pub fn all_intrinsics(&self) -> impl Iterator<Item = &IntrinsicDescriptor> {
self.intrinsics_table.values()
}
pub fn count_available(&self, features: &[MLTargetFeature]) -> usize {
self.intrinsics_table
.values()
.filter(|d| features.contains(&d.required_feature))
.count()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MLLayoutFormat {
NCHW,
NHWC,
CHWN,
NCDHW,
NDHWC,
RowMajor,
ColMajor,
Blocked { block_m: usize, block_n: usize },
VNNIPacked { k_block: usize, n_block: usize },
BF16Packed { k_block: usize, n_block: usize },
AMXTile { rows: usize, col_bytes: usize },
Strided { strides: Vec<isize> },
}
impl MLLayoutFormat {
pub fn name(&self) -> &'static str {
match self {
MLLayoutFormat::NCHW => "NCHW",
MLLayoutFormat::NHWC => "NHWC",
MLLayoutFormat::CHWN => "CHWN",
MLLayoutFormat::NCDHW => "NCDHW",
MLLayoutFormat::NDHWC => "NDHWC",
MLLayoutFormat::RowMajor => "RowMajor",
MLLayoutFormat::ColMajor => "ColMajor",
MLLayoutFormat::Blocked { .. } => "Blocked",
MLLayoutFormat::VNNIPacked { .. } => "VNNIPacked",
MLLayoutFormat::BF16Packed { .. } => "BF16Packed",
MLLayoutFormat::AMXTile { .. } => "AMXTile",
MLLayoutFormat::Strided { .. } => "Strided",
}
}
pub fn is_image_format(&self) -> bool {
matches!(
self,
MLLayoutFormat::NCHW | MLLayoutFormat::NHWC | MLLayoutFormat::CHWN
)
}
pub fn is_3d_format(&self) -> bool {
matches!(self, MLLayoutFormat::NCDHW | MLLayoutFormat::NDHWC)
}
}
pub struct X86MLLayout {
op_layout_prefs: HashMap<MLOpKind, MLLayoutFormat>,
cache_block_sizes: Vec<CacheBlockSize>,
transform_costs: HashMap<(MLLayoutFormat, MLLayoutFormat), f64>,
}
#[derive(Debug, Clone)]
pub struct CacheBlockSize {
pub level: CacheLevel,
pub size_bytes: usize,
pub block_m: usize,
pub block_n: usize,
pub block_k: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheLevel {
L1,
L2,
L3,
}
impl X86MLLayout {
pub fn new() -> Self {
let mut layout = Self {
op_layout_prefs: HashMap::new(),
cache_block_sizes: Vec::new(),
transform_costs: HashMap::new(),
};
layout.init_layout_prefs();
layout.init_cache_blocks();
layout.init_transform_costs();
layout
}
fn init_layout_prefs(&mut self) {
self.op_layout_prefs
.insert(MLOpKind::Conv2D, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::Conv3D, MLLayoutFormat::NDHWC);
self.op_layout_prefs
.insert(MLOpKind::Conv2DDepthwise, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::MatMul, MLLayoutFormat::RowMajor);
self.op_layout_prefs
.insert(MLOpKind::ReLU, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::GELU, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::MaxPool2D, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::AvgPool2D, MLLayoutFormat::NHWC);
self.op_layout_prefs
.insert(MLOpKind::LayerNorm, MLLayoutFormat::RowMajor);
self.op_layout_prefs
.insert(MLOpKind::BatchNorm, MLLayoutFormat::NHWC);
self.op_layout_prefs.insert(
MLOpKind::ScaledDotProductAttention,
MLLayoutFormat::Blocked {
block_m: 64,
block_n: 64,
},
);
}
fn init_cache_blocks(&mut self) {
self.cache_block_sizes.push(CacheBlockSize {
level: CacheLevel::L1,
size_bytes: 32 * 1024,
block_m: 64,
block_n: 256,
block_k: 64,
});
self.cache_block_sizes.push(CacheBlockSize {
level: CacheLevel::L2,
size_bytes: 256 * 1024,
block_m: 128,
block_n: 512,
block_k: 128,
});
self.cache_block_sizes.push(CacheBlockSize {
level: CacheLevel::L3,
size_bytes: 8 * 1024 * 1024,
block_m: 256,
block_n: 1024,
block_k: 256,
});
}
fn init_transform_costs(&mut self) {
self.transform_costs
.insert((MLLayoutFormat::NCHW, MLLayoutFormat::NHWC), 1.0);
self.transform_costs
.insert((MLLayoutFormat::NHWC, MLLayoutFormat::NCHW), 1.0);
self.transform_costs
.insert((MLLayoutFormat::RowMajor, MLLayoutFormat::ColMajor), 2.0);
self.transform_costs.insert(
(
MLLayoutFormat::RowMajor,
MLLayoutFormat::Blocked {
block_m: 64,
block_n: 64,
},
),
3.0,
);
self.transform_costs.insert(
(
MLLayoutFormat::RowMajor,
MLLayoutFormat::VNNIPacked {
k_block: 64,
n_block: 32,
},
),
5.0,
);
}
pub fn preferred_layout(&self, op: MLOpKind) -> MLLayoutFormat {
self.op_layout_prefs
.get(&op)
.cloned()
.unwrap_or(MLLayoutFormat::RowMajor)
}
pub fn block_size(&self, level: CacheLevel) -> Option<&CacheBlockSize> {
self.cache_block_sizes.iter().find(|b| b.level == level)
}
pub fn l1_block(&self) -> &CacheBlockSize {
self.block_size(CacheLevel::L1).unwrap()
}
pub fn l2_block(&self) -> &CacheBlockSize {
self.block_size(CacheLevel::L2).unwrap()
}
pub fn l3_block(&self) -> &CacheBlockSize {
self.block_size(CacheLevel::L3).unwrap()
}
pub fn transform_cost(&self, from: MLLayoutFormat, to: MLLayoutFormat) -> f64 {
self.transform_costs
.get(&(from, to))
.copied()
.unwrap_or(0.0)
}
pub fn is_transform_profitable(
&self,
from: MLLayoutFormat,
to: MLLayoutFormat,
num_ops: usize,
op_cost_per_elem: f64,
numel: usize,
) -> bool {
let transform_cost = self.transform_cost(from, to) * numel as f64;
let compute_savings = (op_cost_per_elem * 0.3) * numel as f64 * num_ops as f64;
compute_savings > transform_cost
}
pub fn compute_strides(shape: &[usize], layout: MLLayoutFormat) -> Vec<isize> {
match layout {
MLLayoutFormat::RowMajor => {
let mut strides = vec![1isize; shape.len()];
for i in (0..shape.len() - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1] as isize;
}
strides
}
MLLayoutFormat::ColMajor => {
let mut strides = vec![1isize; shape.len()];
for i in 1..shape.len() {
strides[i] = strides[i - 1] * shape[i - 1] as isize;
}
strides
}
MLLayoutFormat::NCHW => {
Self::compute_strides(shape, MLLayoutFormat::RowMajor)
}
MLLayoutFormat::NHWC => {
Self::compute_strides(shape, MLLayoutFormat::RowMajor)
}
_ => Self::compute_strides(shape, MLLayoutFormat::RowMajor),
}
}
pub fn vnni_packed_layout(&self) -> MLLayoutFormat {
MLLayoutFormat::VNNIPacked {
k_block: GEMM_BLOCK_K,
n_block: 32,
}
}
pub fn bf16_packed_layout(&self) -> MLLayoutFormat {
MLLayoutFormat::BF16Packed {
k_block: 32,
n_block: 32,
}
}
pub fn amx_tile_layout(&self, rows: usize, col_bytes: usize) -> MLLayoutFormat {
MLLayoutFormat::AMXTile { rows, col_bytes }
}
pub fn recommend_layout(
&self,
ops: &[MLOpKind],
dtype: MLDataType,
features: &[MLTargetFeature],
) -> MLLayoutFormat {
if features.contains(&MLTargetFeature::AMXINT8) {
return MLLayoutFormat::AMXTile {
rows: 16,
col_bytes: 64,
};
}
if features.contains(&MLTargetFeature::AVX512VNNI) && dtype.is_quantized() {
return self.vnni_packed_layout();
}
if features.contains(&MLTargetFeature::AVX512BF16) && dtype == MLDataType::BF16 {
return self.bf16_packed_layout();
}
let mut votes: HashMap<MLLayoutFormat, usize> = HashMap::new();
for op in ops {
let pref = self.preferred_layout(*op);
*votes.entry(pref).or_insert(0) += 1;
}
votes
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(layout, _)| layout)
.unwrap_or(MLLayoutFormat::RowMajor)
}
}
impl Default for X86MLLayout {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MLGraphNode {
pub id: usize,
pub op: MLOpKind,
pub inputs: Vec<usize>,
pub outputs: Vec<usize>,
pub shape: TensorShape,
pub dtype: MLDataType,
pub attributes: HashMap<String, MLAttribute>,
}
#[derive(Debug, Clone)]
pub enum MLAttribute {
Int(i64),
Float(f64),
String(String),
Ints(Vec<i64>),
Floats(Vec<f64>),
Strings(Vec<String>),
}
#[derive(Debug, Clone)]
pub struct MLGraph {
pub nodes: Vec<MLGraphNode>,
pub edges: Vec<(usize, usize)>,
pub name: String,
}
pub struct X86MLGraph {
pub max_feature: MLTargetFeature,
pub opt_level: MLOptLevel,
fusion_patterns: HashMap<Vec<MLOpKind>, FusedOp>,
memory_pool: MemoryPool,
kernel_registry: HashMap<(MLOpKind, MLDataType), Vec<String>>,
auto_tune_config: AutoTuneConfig,
}
#[derive(Debug, Clone)]
pub struct MemoryPool {
pub total_bytes: usize,
pub peak_bytes: usize,
pub allocations: Vec<MemAlloc>,
pub reuse_map: HashMap<usize, usize>,
}
#[derive(Debug, Clone)]
pub struct MemAlloc {
pub id: usize,
pub size_bytes: usize,
pub offset: usize,
pub lifetime_start: usize,
pub lifetime_end: usize,
}
#[derive(Debug, Clone)]
pub struct AutoTuneConfig {
pub enabled: bool,
pub max_trials: usize,
pub warmup_iterations: usize,
pub measure_iterations: usize,
pub target_efficiency: f64,
pub block_size_candidates: Vec<(usize, usize, usize)>,
}
impl Default for AutoTuneConfig {
fn default() -> Self {
Self {
enabled: true,
max_trials: 50,
warmup_iterations: 5,
measure_iterations: 20,
target_efficiency: 0.85,
block_size_candidates: vec![
(16, 16, 16),
(32, 32, 32),
(64, 64, 64),
(64, 256, 64),
(128, 128, 128),
(256, 256, 256),
],
}
}
}
impl X86MLGraph {
pub fn new(max_feature: MLTargetFeature, opt_level: MLOptLevel) -> Self {
let mut graph = Self {
max_feature,
opt_level,
fusion_patterns: HashMap::new(),
memory_pool: MemoryPool {
total_bytes: 0,
peak_bytes: 0,
allocations: Vec::new(),
reuse_map: HashMap::new(),
},
kernel_registry: HashMap::new(),
auto_tune_config: AutoTuneConfig::default(),
};
graph.init_fusion_patterns();
graph.init_kernel_registry();
graph
}
fn init_fusion_patterns(&mut self) {
self.fusion_patterns.insert(
vec![MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU],
FusedOp {
name: "ConvBNReLU".into(),
ops: vec![MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU],
fused_kernel: "x86_fused_conv_bn_relu".into(),
estimated_speedup: 1.4,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::Conv2D, MLOpKind::ReLU],
FusedOp {
name: "ConvReLU".into(),
ops: vec![MLOpKind::Conv2D, MLOpKind::ReLU],
fused_kernel: "x86_fused_conv_relu".into(),
estimated_speedup: 1.2,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::MatMul, MLOpKind::Add, MLOpKind::ReLU],
FusedOp {
name: "LinearReLU".into(),
ops: vec![MLOpKind::MatMul, MLOpKind::Add, MLOpKind::ReLU],
fused_kernel: "x86_fused_linear_relu".into(),
estimated_speedup: 1.3,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::MatMul, MLOpKind::Add],
FusedOp {
name: "LinearAdd".into(),
ops: vec![MLOpKind::MatMul, MLOpKind::Add],
fused_kernel: "x86_fused_linear_bias".into(),
estimated_speedup: 1.15,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::LayerNorm, MLOpKind::MatMul],
FusedOp {
name: "NormLinear".into(),
ops: vec![MLOpKind::LayerNorm, MLOpKind::MatMul],
fused_kernel: "x86_fused_layernorm_linear".into(),
estimated_speedup: 1.25,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::Add, MLOpKind::LayerNorm],
FusedOp {
name: "ResidualNorm".into(),
ops: vec![MLOpKind::Add, MLOpKind::LayerNorm],
fused_kernel: "x86_fused_residual_layernorm".into(),
estimated_speedup: 1.3,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::MatMul, MLOpKind::SiLU],
FusedOp {
name: "LinearSiLU".into(),
ops: vec![MLOpKind::MatMul, MLOpKind::SiLU],
fused_kernel: "x86_fused_linear_silu".into(),
estimated_speedup: 1.25,
},
);
self.fusion_patterns.insert(
vec![MLOpKind::MatMul, MLOpKind::GELU],
FusedOp {
name: "LinearGELU".into(),
ops: vec![MLOpKind::MatMul, MLOpKind::GELU],
fused_kernel: "x86_fused_linear_gelu".into(),
estimated_speedup: 1.25,
},
);
}
fn init_kernel_registry(&mut self) {
self.kernel_registry.insert(
(MLOpKind::MatMul, MLDataType::FP32),
vec![
"x86_gemm_sse2_m4n4k4".into(),
"x86_gemm_avx2_m8n8k8".into(),
"x86_gemm_avx512_m16n16k16".into(),
],
);
self.kernel_registry.insert(
(MLOpKind::MatMul, MLDataType::INT8),
vec![
"x86_gemm_vnni_int8_m32n32k64".into(),
"x86_gemm_amx_int8_m64n64k64".into(),
],
);
self.kernel_registry.insert(
(MLOpKind::Conv2D, MLDataType::FP32),
vec![
"x86_conv2d_direct_sse".into(),
"x86_conv2d_im2col_gemm_avx2".into(),
"x86_conv2d_im2col_gemm_avx512".into(),
"x86_conv2d_winograd_f2x2_3x3".into(),
],
);
}
pub fn try_fuse(&self, ops: &[MLOpKind]) -> Option<FusedOp> {
self.fusion_patterns.get(ops).cloned()
}
pub fn find_fusions(&self, graph: &MLGraph) -> Vec<FusedOp> {
let mut fusions = Vec::new();
for window_size in (2..=4).rev() {
for i in 0..graph.nodes.len().saturating_sub(window_size - 1) {
let slice: Vec<MLOpKind> = graph.nodes[i..i + window_size]
.iter()
.map(|n| n.op)
.collect();
if let Some(fused) = self.try_fuse(&slice) {
fusions.push(fused);
}
}
}
fusions
}
pub fn select_kernel(
&self,
op: MLOpKind,
dtype: MLDataType,
_m: usize,
_n: usize,
_k: usize,
features: &[MLTargetFeature],
) -> Option<String> {
let kernels = self.kernel_registry.get(&(op, dtype))?;
for kernel in kernels {
if features.contains(&MLTargetFeature::AMXINT8) && kernel.contains("amx") {
return Some(kernel.clone());
}
if features.contains(&MLTargetFeature::AVX512VNNI) && kernel.contains("vnni") {
return Some(kernel.clone());
}
if features.contains(&MLTargetFeature::AVX512F) && kernel.contains("avx512") {
return Some(kernel.clone());
}
if features.contains(&MLTargetFeature::AVX2) && kernel.contains("avx2") {
return Some(kernel.clone());
}
}
kernels.first().cloned()
}
pub fn plan_memory(&mut self, graph: &MLGraph) -> MemoryPool {
let mut pool = MemoryPool {
total_bytes: 0,
peak_bytes: 0,
allocations: Vec::new(),
reuse_map: HashMap::new(),
};
let mut current_bytes = 0usize;
let mut peak = 0usize;
for (idx, node) in graph.nodes.iter().enumerate() {
let size = node.shape.numel() * node.dtype.size_bytes();
let alloc = MemAlloc {
id: idx,
size_bytes: size,
offset: current_bytes,
lifetime_start: idx,
lifetime_end: idx + 3, };
let mut freed = 0usize;
let mut i = 0;
while i < pool.allocations.len() {
if pool.allocations[i].lifetime_end <= idx {
freed += pool.allocations[i].size_bytes;
pool.allocations.remove(i);
} else {
i += 1;
}
}
current_bytes = current_bytes.saturating_sub(freed);
pool.allocations.push(alloc);
current_bytes += size;
if current_bytes > peak {
peak = current_bytes;
}
}
pool.total_bytes = current_bytes;
pool.peak_bytes = peak;
pool
}
pub fn auto_tune_config(&self) -> &AutoTuneConfig {
&self.auto_tune_config
}
pub fn set_auto_tune_config(&mut self, config: AutoTuneConfig) {
self.auto_tune_config = config;
}
pub fn register_kernel(&mut self, op: MLOpKind, dtype: MLDataType, kernel: String) {
self.kernel_registry
.entry((op, dtype))
.or_default()
.push(kernel);
}
pub fn kernels_for(&self, op: MLOpKind, dtype: MLDataType) -> Option<&Vec<String>> {
self.kernel_registry.get(&(op, dtype))
}
pub fn memory_pool(&self) -> &MemoryPool {
&self.memory_pool
}
pub fn fusion_patterns(&self) -> &HashMap<Vec<MLOpKind>, FusedOp> {
&self.fusion_patterns
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuantMode {
PostTraining,
QAT,
Dynamic,
Static,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuantGranularity {
PerTensor,
PerChannel,
PerGroup { group_size: usize },
}
#[derive(Debug, Clone)]
pub struct QuantizedOp {
pub op: MLOpKind,
pub input_dtype: MLDataType,
pub weight_dtype: MLDataType,
pub output_dtype: MLDataType,
pub input_scale: Option<f32>,
pub weight_scale: Option<f32>,
pub output_scale: Option<f32>,
pub input_zero_point: Option<i32>,
pub weight_zero_point: Option<i32>,
pub output_zero_point: Option<i32>,
pub quant_mode: QuantMode,
pub granularity: QuantGranularity,
pub kernel_name: String,
}
#[derive(Debug, Clone)]
pub struct QuantCalibration {
pub min_vals: Vec<f32>,
pub max_vals: Vec<f32>,
pub histograms: Vec<Vec<usize>>,
pub num_bins: usize,
}
pub struct X86MLQuantization {
pub modes: Vec<QuantMode>,
pub calibration_data: Option<QuantCalibration>,
pub default_granularity: QuantGranularity,
}
impl X86MLQuantization {
pub fn new() -> Self {
Self {
modes: vec![
QuantMode::PostTraining,
QuantMode::QAT,
QuantMode::Dynamic,
QuantMode::Static,
],
calibration_data: None,
default_granularity: QuantGranularity::PerTensor,
}
}
pub fn quantize(
&self,
op: MLOpKind,
src_dtype: MLDataType,
dst_dtype: MLDataType,
) -> Result<QuantizedOp, MLError> {
if !dst_dtype.is_quantized()
&& dst_dtype != MLDataType::FP16
&& dst_dtype != MLDataType::BF16
{
return Err(MLError::QuantizationFailed(format!(
"Target dtype {:?} is not a quantized type",
dst_dtype
)));
}
let mode = if dst_dtype.is_quantized() {
if src_dtype.is_floating() {
QuantMode::Static
} else {
QuantMode::PostTraining
}
} else {
QuantMode::PostTraining
};
let (input_scale, output_scale, input_zp, output_zp) =
self.default_scales(src_dtype, dst_dtype);
let kernel_name = format!(
"x86_quant_{}_{}_to_{}",
op.name().to_lowercase(),
src_dtype,
dst_dtype,
);
Ok(QuantizedOp {
op,
input_dtype: src_dtype,
weight_dtype: dst_dtype,
output_dtype: if dst_dtype.is_quantized() {
MLDataType::INT32
} else {
dst_dtype
},
input_scale,
weight_scale: None,
output_scale,
input_zero_point: input_zp,
weight_zero_point: None,
output_zero_point: output_zp,
quant_mode: mode,
granularity: self.default_granularity,
kernel_name,
})
}
fn default_scales(
&self,
src: MLDataType,
dst: MLDataType,
) -> (Option<f32>, Option<f32>, Option<i32>, Option<i32>) {
match dst {
MLDataType::INT8 => {
let scale = if src == MLDataType::FP32 {
Some(1.0 / 127.0)
} else {
Some(1.0 / 127.0)
};
(scale, scale, Some(0), Some(0))
}
MLDataType::UINT8 => {
let scale = Some(1.0 / 255.0);
(scale, scale, Some(0), Some(128))
}
MLDataType::INT4 => {
let scale = Some(1.0 / 7.0);
(scale, scale, Some(0), Some(0))
}
MLDataType::FP16 => {
(None, None, None, None)
}
MLDataType::BF16 => (None, None, None, None),
_ => (Some(1.0 / 127.0), Some(1.0 / 127.0), Some(0), Some(0)),
}
}
pub fn calibrate(&mut self, samples: &[Vec<f32>], num_bins: usize) {
let mut calibration = QuantCalibration {
min_vals: Vec::new(),
max_vals: Vec::new(),
histograms: Vec::new(),
num_bins,
};
for sample in samples {
if sample.is_empty() {
continue;
}
let min = sample.iter().cloned().fold(f32::INFINITY, f32::min);
let max = sample.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
calibration.min_vals.push(min);
calibration.max_vals.push(max);
let mut hist = vec![0usize; num_bins];
if (max - min).abs() > f32::EPSILON {
for &val in sample {
let bin = ((val - min) / (max - min) * (num_bins - 1) as f32) as usize;
let bin = bin.min(num_bins - 1);
hist[bin] += 1;
}
}
calibration.histograms.push(hist);
}
self.calibration_data = Some(calibration);
}
pub fn compute_scale_minmax(
&self,
tensor_idx: usize,
target_dtype: MLDataType,
) -> Option<(f32, i32)> {
let calib = self.calibration_data.as_ref()?;
let min = *calib.min_vals.get(tensor_idx)?;
let max = *calib.max_vals.get(tensor_idx)?;
let qmin = self.quant_min(target_dtype);
let qmax = self.quant_max(target_dtype);
let scale = (max - min) / (qmax - qmin) as f32;
let zero_point = qmin - (min / scale).round() as i32;
Some((scale, zero_point.clamp(qmin, qmax)))
}
pub fn compute_scale_mse(
&self,
tensor_idx: usize,
target_dtype: MLDataType,
) -> Option<(f32, i32)> {
let calib = self.calibration_data.as_ref()?;
let min = *calib.min_vals.get(tensor_idx)?;
let max = *calib.max_vals.get(tensor_idx)?;
let hist = calib.histograms.get(tensor_idx)?;
let qmin = self.quant_min(target_dtype);
let qmax = self.quant_max(target_dtype);
let mut best_scale = (max - min) / (qmax - qmin) as f32;
let mut best_mse = f32::MAX;
for i in 0..100 {
let scale = best_scale * (0.5 + i as f32 * 0.01);
let mut mse = 0.0f32;
for (bin, &count) in hist.iter().enumerate() {
if count == 0 {
continue;
}
let val = min + (max - min) * bin as f32 / (hist.len() - 1) as f32;
let quantized = (val / scale).round().clamp(qmin as f32, qmax as f32);
let dequantized = quantized * scale;
let err = val - dequantized;
mse += err * err * count as f32;
}
if mse < best_mse {
best_mse = mse;
best_scale = scale;
}
}
let zero_point = qmin - (min / best_scale).round() as i32;
Some((best_scale, zero_point.clamp(qmin, qmax)))
}
fn quant_min(&self, dtype: MLDataType) -> i32 {
match dtype {
MLDataType::INT8 => -128,
MLDataType::UINT8 => 0,
MLDataType::INT4 => -8,
MLDataType::UINT4 => 0,
_ => -128,
}
}
fn quant_max(&self, dtype: MLDataType) -> i32 {
match dtype {
MLDataType::INT8 => 127,
MLDataType::UINT8 => 255,
MLDataType::INT4 => 7,
MLDataType::UINT4 => 15,
_ => 127,
}
}
pub fn supports_mode(&self, mode: QuantMode) -> bool {
self.modes.contains(&mode)
}
pub fn calibration(&self) -> Option<&QuantCalibration> {
self.calibration_data.as_ref()
}
pub fn dequantize(
&self,
val: f32,
scale: f32,
zero_point: i32,
target_dtype: MLDataType,
) -> f32 {
let qmin = self.quant_min(target_dtype);
let qmax = self.quant_max(target_dtype);
let quantized = (val / scale).round() as i32 + zero_point;
let clamped = quantized.clamp(qmin, qmax);
(clamped - zero_point) as f32 * scale
}
}
impl Default for X86MLQuantization {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MLRuntime {
ONNX,
OpenVINO,
TFLiteMicro,
Native,
GGML,
}
impl MLRuntime {
pub fn name(&self) -> &'static str {
match self {
MLRuntime::ONNX => "ONNX Runtime",
MLRuntime::OpenVINO => "OpenVINO",
MLRuntime::TFLiteMicro => "TFLite Micro",
MLRuntime::Native => "Native X86",
MLRuntime::GGML => "GGML/GGUF",
}
}
}
#[derive(Debug, Clone)]
pub struct CustomOperator {
pub name: String,
pub domain: String,
pub version: i64,
pub op_type: MLOpKind,
pub input_types: Vec<MLDataType>,
pub output_types: Vec<MLDataType>,
pub kernel_name: String,
}
pub struct X86MLRuntime {
pub custom_ops: Vec<CustomOperator>,
pub supported_runtimes: Vec<MLRuntime>,
pub config: RuntimeConfig,
}
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub num_threads: usize,
pub intra_op_parallelism: usize,
pub inter_op_parallelism: usize,
pub enable_profiling: bool,
pub enable_graph_optimization: bool,
pub memory_limit_mb: usize,
pub log_severity: LogSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogSeverity {
Verbose,
Info,
Warning,
Error,
Fatal,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
num_threads: num_cpus::get(),
intra_op_parallelism: 0,
inter_op_parallelism: 0,
enable_profiling: false,
enable_graph_optimization: true,
memory_limit_mb: 4096,
log_severity: LogSeverity::Warning,
}
}
}
impl X86MLRuntime {
pub fn new() -> Self {
Self {
custom_ops: Vec::new(),
supported_runtimes: vec![
MLRuntime::ONNX,
MLRuntime::OpenVINO,
MLRuntime::TFLiteMicro,
MLRuntime::Native,
MLRuntime::GGML,
],
config: RuntimeConfig::default(),
}
}
pub fn register_custom_op(&mut self, op: CustomOperator) {
self.custom_ops.push(op);
}
pub fn custom_operators(&self) -> &[CustomOperator] {
&self.custom_ops
}
pub fn supports_runtime(&self, runtime: MLRuntime) -> bool {
self.supported_runtimes.contains(&runtime)
}
pub fn emit_onnx_custom_op_registration(&self, op: &CustomOperator) -> String {
format!(
r#"// ONNX Runtime Custom Op: {name} v{version}
#include <onnxruntime_cxx_api.h>
struct Kernel_{name} : Ort::CustomOpBase<Kernel_{name}, {name}> {{
void Compute(OrtKernelContext* context) {{
// Get inputs
const OrtValue* input = Ort::KernelContext::GetInput(context, 0);
// Allocate output
OrtValue* output = Ort::KernelContext::GetOutput(context, 0, input->GetTensorTypeAndShapeInfo());
// Call native kernel: {kernel}
{kernel}(input, output);
}}
}};
// Registration macro
OrtCustomOp* CreateCustomOp_{name}() {{
return new Kernel_{name}();
}}
"#,
name = op.name,
version = op.version,
kernel = op.kernel_name,
)
}
pub fn emit_openvino_custom_layer(&self, op: &CustomOperator) -> String {
format!(
r#"<!-- OpenVINO Custom Layer: {name} -->
<CustomLayer name="{name}" type="Simple" version="1">
<Kernel>
<Source shape="[1]"/>
<Parameters kernel="{kernel}"/>
</Kernel>
<Parameters>
<Tensor arg-name="input" type="input" port-index="0"/>
<Tensor arg-name="output" type="output" port-index="0"/>
</Parameters>
<WorkSizes dim="input" global="B*C*H*W"/>
</CustomLayer>
"#,
name = op.name,
kernel = op.kernel_name,
)
}
pub fn emit_tflite_micro_registration(&self, op: &CustomOperator) -> String {
format!(
r#"// TFLite Micro Custom Op: {name}
#include "tensorflow/lite/micro/kernels/micro_ops.h"
TfLiteRegistration* Register_{name}() {{
static TfLiteRegistration r = {{
.init = nullptr,
.free = nullptr,
.prepare = {name}_Prepare,
.invoke = {name}_Invoke,
.profiling_string = nullptr,
}};
return &r;
}}
TfLiteStatus {name}_Invoke(TfLiteContext* ctx, TfLiteNode* node) {{
// Call {kernel}
return kTfLiteOk;
}}
"#,
name = op.name,
kernel = op.kernel_name,
)
}
pub fn emit_native_runtime_header(&self) -> String {
format!(
r#"/* Auto-generated X86 ML Native Runtime Header */
#ifndef _X86_ML_NATIVE_RUNTIME_H_
#define _X86_ML_NATIVE_RUNTIME_H_
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {{
#endif
// Runtime configuration
#define X86_ML_MAX_THREADS {num_threads}
#define X86_ML_MEMORY_LIMIT_MB {mem_limit}
// Memory allocation
void* x86_ml_alloc(size_t size);
void x86_ml_free(void* ptr);
void x86_ml_set_threads(int n);
// Operator dispatch
int x86_ml_invoke(const char* op_name, void** inputs, void** outputs);
#ifdef __cplusplus
}}
#endif
#endif /* _X86_ML_NATIVE_RUNTIME_H_ */
"#,
num_threads = self.config.num_threads,
mem_limit = self.config.memory_limit_mb,
)
}
pub fn emit_ggml_op_mapping(&self, ops: &[MLOpKind]) -> String {
let mut out = String::from("// GGML operator mapping for X86\n");
for op in ops {
let ggml_name = match op {
MLOpKind::MatMul => "ggml_mul_mat",
MLOpKind::Add => "ggml_add",
MLOpKind::Mul => "ggml_mul",
MLOpKind::ReLU => "ggml_relu",
MLOpKind::GELU => "ggml_gelu",
MLOpKind::SiLU => "ggml_silu",
MLOpKind::Softmax => "ggml_soft_max",
MLOpKind::LayerNorm => "ggml_norm",
MLOpKind::RMSNorm => "ggml_rms_norm",
MLOpKind::ScaledDotProductAttention => "ggml_flash_attn",
_ => "ggml_custom",
};
out.push_str(&format!(
"#define ML_OP_{} {}\n",
op.name().to_uppercase(),
ggml_name
));
}
out
}
pub fn configure(&mut self, config: RuntimeConfig) {
self.config = config;
}
pub fn configuration(&self) -> &RuntimeConfig {
&self.config
}
}
impl Default for X86MLRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_feature_ranking() {
assert!(MLTargetFeature::AVX.is_at_least(MLTargetFeature::SSE2));
assert!(MLTargetFeature::AVX2.is_at_least(MLTargetFeature::AVX));
assert!(MLTargetFeature::AVX512F.is_at_least(MLTargetFeature::AVX2));
assert!(MLTargetFeature::AVX512VNNI.is_at_least(MLTargetFeature::AVX512F));
assert!(MLTargetFeature::AMXINT8.is_at_least(MLTargetFeature::AVX512VNNI));
assert!(!MLTargetFeature::SSE2.is_at_least(MLTargetFeature::AVX));
}
#[test]
fn test_feature_isa_level() {
assert_eq!(MLTargetFeature::SSE2.isa_level(), "sse2");
assert_eq!(MLTargetFeature::AVX2.isa_level(), "avx2");
assert_eq!(MLTargetFeature::AVX512VNNI.isa_level(), "avx512vnni");
assert_eq!(MLTargetFeature::AVX512BF16.isa_level(), "avx512bf16");
assert_eq!(MLTargetFeature::AMXINT8.isa_level(), "amx-int8");
}
#[test]
fn test_op_kind_names() {
assert_eq!(MLOpKind::MatMul.name(), "MatMul");
assert_eq!(MLOpKind::Conv2D.name(), "Conv2D");
assert_eq!(MLOpKind::ReLU.name(), "ReLU");
assert_eq!(MLOpKind::GELU.name(), "GELU");
assert_eq!(MLOpKind::LayerNorm.name(), "LayerNorm");
assert_eq!(MLOpKind::Add.name(), "Add");
}
#[test]
fn test_is_activation() {
assert!(MLOpKind::ReLU.is_activation());
assert!(MLOpKind::GELU.is_activation());
assert!(MLOpKind::SiLU.is_activation());
assert!(MLOpKind::Sigmoid.is_activation());
assert!(MLOpKind::Tanh.is_activation());
assert!(MLOpKind::Softmax.is_activation());
assert!(!MLOpKind::MatMul.is_activation());
assert!(!MLOpKind::Conv2D.is_activation());
assert!(!MLOpKind::Add.is_activation());
}
#[test]
fn test_is_normalization() {
assert!(MLOpKind::BatchNorm.is_normalization());
assert!(MLOpKind::LayerNorm.is_normalization());
assert!(MLOpKind::GroupNorm.is_normalization());
assert!(MLOpKind::InstanceNorm.is_normalization());
assert!(MLOpKind::RMSNorm.is_normalization());
assert!(!MLOpKind::ReLU.is_normalization());
assert!(!MLOpKind::MatMul.is_normalization());
}
#[test]
fn test_tensor_shape() {
let shape = TensorShape::new(vec![1, 3, 224, 224]);
assert_eq!(shape.rank(), 4);
assert_eq!(shape.numel(), 1 * 3 * 224 * 224);
assert_eq!(shape.dim(0).unwrap(), 1);
assert_eq!(shape.dim(1).unwrap(), 3);
assert_eq!(shape.dim(2).unwrap(), 224);
assert_eq!(shape.dim(3).unwrap(), 224);
assert!(shape.dim(4).is_none());
}
#[test]
fn test_tensor_shape_nchw() {
let shape = TensorShape::new(vec![2, 64, 56, 56]);
let (n, c, h, w) = shape.as_nchw().unwrap();
assert_eq!(n, 2);
assert_eq!(c, 64);
assert_eq!(h, 56);
assert_eq!(w, 56);
}
#[test]
fn test_tensor_shape_display() {
let shape = TensorShape::new(vec![1, 256, 256]);
assert_eq!(format!("{}", shape), "[1, 256, 256]");
}
#[test]
fn test_tensor_shape_is_scalar() {
assert!(TensorShape::new(vec![1]).is_scalar());
assert!(TensorShape::new(vec![]).is_scalar());
assert!(!TensorShape::new(vec![2, 3]).is_scalar());
}
#[test]
fn test_dtype_sizes() {
assert_eq!(MLDataType::FP32.size_bytes(), 4);
assert_eq!(MLDataType::FP16.size_bytes(), 2);
assert_eq!(MLDataType::BF16.size_bytes(), 2);
assert_eq!(MLDataType::INT8.size_bytes(), 1);
assert_eq!(MLDataType::INT4.elem_bits(), 4);
assert_eq!(MLDataType::INT2.elem_bits(), 2);
assert_eq!(MLDataType::INT1.elem_bits(), 1);
}
#[test]
fn test_dtype_is_floating() {
assert!(MLDataType::FP32.is_floating());
assert!(MLDataType::FP16.is_floating());
assert!(MLDataType::BF16.is_floating());
assert!(MLDataType::FP8E4M3.is_floating());
assert!(!MLDataType::INT8.is_floating());
assert!(!MLDataType::INT32.is_floating());
}
#[test]
fn test_dtype_is_integer() {
assert!(MLDataType::INT8.is_integer());
assert!(MLDataType::INT32.is_integer());
assert!(!MLDataType::FP32.is_integer());
assert!(!MLDataType::BF16.is_integer());
}
#[test]
fn test_dtype_is_quantized() {
assert!(MLDataType::INT8.is_quantized());
assert!(MLDataType::UINT8.is_quantized());
assert!(MLDataType::INT4.is_quantized());
assert!(MLDataType::INT2.is_quantized());
assert!(!MLDataType::FP32.is_quantized());
assert!(!MLDataType::FP16.is_quantized());
}
#[test]
fn test_data_type_conversion() {
let dtypes = X86MLDataType::new();
assert!(dtypes.can_convert(MLDataType::FP32, MLDataType::FP16));
assert!(dtypes.can_convert(MLDataType::FP16, MLDataType::FP32));
assert!(dtypes.can_convert(MLDataType::FP32, MLDataType::BF16));
assert!(dtypes.can_convert(MLDataType::FP32, MLDataType::INT8));
assert!(!dtypes.can_convert(MLDataType::FP32, MLDataType::BOOL));
}
#[test]
fn test_optimal_compute_dtype() {
let dtypes = X86MLDataType::new();
assert_eq!(
dtypes.optimal_compute_dtype(&[MLTargetFeature::SSE2]),
MLDataType::FP32
);
assert_eq!(
dtypes.optimal_compute_dtype(&[MLTargetFeature::AVX512VNNI]),
MLDataType::INT8
);
assert_eq!(
dtypes.optimal_compute_dtype(&[MLTargetFeature::AVX512BF16]),
MLDataType::BF16
);
assert_eq!(
dtypes.optimal_compute_dtype(&[MLTargetFeature::AVX512FP16]),
MLDataType::FP16
);
}
#[test]
fn test_accumulator_dtype() {
let dtypes = X86MLDataType::new();
assert_eq!(
dtypes.accumulator_dtype(MLDataType::INT8),
MLDataType::INT32
);
assert_eq!(dtypes.accumulator_dtype(MLDataType::FP16), MLDataType::FP32);
assert_eq!(dtypes.accumulator_dtype(MLDataType::BF16), MLDataType::FP32);
assert_eq!(dtypes.accumulator_dtype(MLDataType::FP32), MLDataType::FP32);
}
#[test]
fn test_ml_support_creation() {
let features = vec![MLTargetFeature::AVX2, MLTargetFeature::AVX512F];
let ml = X86MLSupport::new(features, MLOptLevel::O2);
assert!(ml.has_feature(MLTargetFeature::AVX2));
assert!(ml.has_feature(MLTargetFeature::AVX512F));
assert!(!ml.has_feature(MLTargetFeature::AVX512VNNI));
assert_eq!(ml.max_feature(), MLTargetFeature::AVX512F);
}
#[test]
fn test_ml_support_lower_tensor_op() {
let features = vec![MLTargetFeature::AVX2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let result = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![1, 64, 64]),
&TensorShape::new(vec![64, 128]),
&TensorShape::new(vec![1, 64, 128]),
);
assert!(result.is_ok());
let r = result.unwrap();
assert_eq!(r.op, MLOpKind::MatMul);
assert!(r.vectorized);
assert!(r.isa_used.is_some());
}
#[test]
fn test_ml_support_conv2d() {
let features = vec![MLTargetFeature::AVX512F];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let result = ml.lower_tensor_op(
MLOpKind::Conv2D,
&TensorShape::new(vec![1, 3, 224, 224]),
&TensorShape::new(vec![64, 3, 3, 3]),
&TensorShape::new(vec![1, 64, 222, 222]),
);
assert!(result.is_ok());
let r = result.unwrap();
assert!(r.kernel_name.contains("conv"));
assert!(r.vectorized);
}
#[test]
fn test_ml_support_unsupported_op() {
let features = vec![MLTargetFeature::SSE2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O0);
let result = ml.lower_tensor_op(
MLOpKind::Pow,
&TensorShape::new(vec![10, 10]),
&TensorShape::new(vec![10, 10]),
&TensorShape::new(vec![10, 10]),
);
assert!(result.is_ok());
}
#[test]
fn test_ml_support_fusion() {
let features = vec![MLTargetFeature::AVX2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let fused = ml.try_fuse_ops(&[MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU]);
assert!(fused.is_some());
let f = fused.unwrap();
assert_eq!(f.name, "ConvBNReLU");
assert!(f.estimated_speedup > 1.0);
}
#[test]
fn test_ml_support_no_fusion() {
let features = vec![MLTargetFeature::SSE2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O0);
let fused = ml.try_fuse_ops(&[MLOpKind::Conv2D, MLOpKind::Conv2D]);
assert!(fused.is_none());
}
#[test]
fn test_ml_support_summary() {
let features = vec![MLTargetFeature::AVX2];
let ml = X86MLSupport::new(features, MLOptLevel::O2);
let summary = ml.summary();
assert!(summary.contains("Operations lowered"));
assert!(summary.contains("Fused ops"));
assert!(summary.contains("Vectorized ops"));
assert!(summary.contains("Quantized ops"));
}
#[test]
fn test_tensor_ops_gemm_kernels() {
let ops = X86TensorOps::new(MLTargetFeature::AVX512F);
let kernels = ops.gemm_kernels();
assert!(!kernels.is_empty());
assert!(kernels.len() >= 6);
}
#[test]
fn test_tensor_ops_winograd_config() {
let ops = X86TensorOps::new(MLTargetFeature::AVX512F);
let cfg = ops.winograd_config(2, 3);
assert!(cfg.is_some());
let c = cfg.unwrap();
assert_eq!(c.f_m, 2);
assert_eq!(c.f_r, 3);
assert_eq!(c.tile_size, 4);
assert_eq!(c.transform_a.len(), 16); assert_eq!(c.transform_b.len(), 12); assert_eq!(c.transform_c.len(), 8); }
#[test]
fn test_tensor_ops_kernel_caching() {
let mut ops = X86TensorOps::new(MLTargetFeature::AVX2);
let features = vec![MLTargetFeature::AVX2];
let a = TensorShape::new(vec![4, 8]);
let b = TensorShape::new(vec![8, 16]);
let c = TensorShape::new(vec![4, 16]);
let r1 = ops.lower(MLOpKind::MatMul, &a, &b, &c, &features).unwrap();
let r2 = ops.lower(MLOpKind::MatMul, &a, &b, &c, &features).unwrap();
assert_eq!(r1.kernel_name, r2.kernel_name);
}
#[test]
fn test_tensor_ops_activation_lowering() {
let mut ops = X86TensorOps::new(MLTargetFeature::AVX2);
let features = vec![MLTargetFeature::AVX2];
for op in &[
MLOpKind::ReLU,
MLOpKind::GELU,
MLOpKind::SiLU,
MLOpKind::Sigmoid,
MLOpKind::Tanh,
] {
let r = ops.lower(
*op,
&TensorShape::new(vec![16, 64]),
&TensorShape::new(vec![16, 64]),
&TensorShape::new(vec![16, 64]),
&features,
);
assert!(r.is_ok(), "Failed to lower {:?}", op);
assert!(r.unwrap().vectorized);
}
}
#[test]
fn test_tensor_ops_pooling_lowering() {
let mut ops = X86TensorOps::new(MLTargetFeature::AVX2);
let features = vec![MLTargetFeature::AVX2];
let r = ops.lower(
MLOpKind::MaxPool2D,
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![1, 64, 28, 28]),
&TensorShape::new(vec![1, 64, 28, 28]),
&features,
);
assert!(r.is_ok());
assert!(r.unwrap().kernel_name.contains("maxpool"));
}
#[test]
fn test_tensor_ops_norm_lowering() {
let mut ops = X86TensorOps::new(MLTargetFeature::AVX2);
let features = vec![MLTargetFeature::AVX2];
for op in &[MLOpKind::LayerNorm, MLOpKind::BatchNorm, MLOpKind::RMSNorm] {
let r = ops.lower(
*op,
&TensorShape::new(vec![2, 128, 64]),
&TensorShape::new(vec![2, 128, 64]),
&TensorShape::new(vec![2, 128, 64]),
&features,
);
assert!(r.is_ok(), "Failed to lower {:?}", op);
}
}
#[test]
fn test_tensor_ops_reduce_lowering() {
let mut ops = X86TensorOps::new(MLTargetFeature::AVX2);
let features = vec![MLTargetFeature::AVX2];
let r = ops.lower(
MLOpKind::ReduceSum,
&TensorShape::new(vec![4, 128, 256]),
&TensorShape::new(vec![4, 1, 256]),
&TensorShape::new(vec![4, 1, 256]),
&features,
);
assert!(r.is_ok());
assert!(r.unwrap().kernel_name.contains("reduce"));
}
#[test]
fn test_vector_ml_creation() {
let vml = X86VectorML::new(MLTargetFeature::AVX512VNNI);
assert!(vml.has_vnni());
assert!(!vml.has_bf16());
assert!(!vml.has_fp16());
assert!(!vml.has_amx());
}
#[test]
fn test_vector_ml_amx_creation() {
let vml = X86VectorML::new(MLTargetFeature::AMXINT8);
assert!(vml.has_amx());
assert!(vml.has_vnni());
assert!(!vml.has_bf16());
}
#[test]
fn test_vector_ml_bf16_creation() {
let vml = X86VectorML::new(MLTargetFeature::AVX512BF16);
assert!(vml.has_bf16());
assert!(!vml.has_vnni());
assert!(!vml.has_fp16());
}
#[test]
fn test_vector_ml_fp16_creation() {
let vml = X86VectorML::new(MLTargetFeature::AVX512FP16);
assert!(vml.has_fp16());
assert!(!vml.has_bf16());
assert!(!vml.has_vnni());
}
#[test]
fn test_vector_width() {
let vml = X86VectorML::new(MLTargetFeature::AVX512F);
let (bytes, lanes) = vml.vector_width(MLTargetFeature::AVX512F, MLDataType::FP32);
assert_eq!(bytes, 64);
assert_eq!(lanes, 16);
let (bytes, lanes) = vml.vector_width(MLTargetFeature::AVX2, MLDataType::FP32);
assert_eq!(bytes, 32);
assert_eq!(lanes, 8);
let (bytes, lanes) = vml.vector_width(MLTargetFeature::SSE2, MLDataType::FP32);
assert_eq!(bytes, 16);
assert_eq!(lanes, 4);
let (bytes, lanes) = vml.vector_width(MLTargetFeature::AVX512BF16, MLDataType::BF16);
assert_eq!(bytes, 64);
assert_eq!(lanes, 32);
}
#[test]
fn test_vector_ml_gemm_asm() {
let vml = X86VectorML::new(MLTargetFeature::AVX512F);
let asm = vml.emit_gemm_asm(64, 128, 256, MLDataType::FP32);
assert!(asm.contains(".L_gemm_fp32"));
assert!(asm.contains("vfmadd"));
}
#[test]
fn test_vector_ml_vnni_asm() {
let vml = X86VectorML::new(MLTargetFeature::AVX512VNNI);
let asm = vml.emit_gemm_asm(64, 128, 256, MLDataType::INT8);
assert!(asm.contains("vpdpbusd"));
assert!(asm.contains(".L_gemm_vnni"));
}
#[test]
fn test_vector_ml_amx_asm() {
let vml = X86VectorML::new(MLTargetFeature::AMXINT8);
let config_asm = vml.emit_amx_config_asm(16, 64);
assert!(config_asm.contains("ldtilecfg"));
let gemm_asm = vml.emit_amx_gemm_asm(64, 64, 64, "int8");
assert!(gemm_asm.contains("tdpbssd"));
assert!(gemm_asm.contains("tileloadd"));
assert!(gemm_asm.contains("tilestored"));
}
#[test]
fn test_intrinsics_lookup() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
assert!(intrin.lookup("__builtin_ia32_vpdpbusd").is_some());
assert!(intrin.lookup("__builtin_ia32_vpdpwssd").is_some());
assert!(intrin.lookup("__builtin_ia32_vcvtne2ps2bf16").is_some());
assert!(intrin.lookup("__builtin_ia32_vfmadd132ph").is_some());
assert!(intrin.lookup("__builtin_ia32_tileloadd64").is_some());
assert!(intrin.lookup("__builtin_ia32_tdpbssd").is_some());
assert!(intrin.lookup("nonexistent").is_none());
}
#[test]
fn test_intrinsics_availability() {
let features = vec![MLTargetFeature::AVX512VNNI, MLTargetFeature::AVX512BF16];
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
assert!(intrin.is_available("__builtin_ia32_vpdpbusd", &features));
assert!(intrin.is_available("__builtin_ia32_vdpbf16ps", &features));
assert!(!intrin.is_available("__builtin_ia32_vfmadd132ph", &features));
assert!(!intrin.is_available("__builtin_ia32_tileloadd64", &features));
}
#[test]
fn test_intrinsics_by_category() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AMXINT8);
let vnni = intrin.intrinsics_by_category(IntrinsicCategory::VNNI);
assert!(!vnni.is_empty());
let bf16 = intrin.intrinsics_by_category(IntrinsicCategory::BF16);
assert!(!bf16.is_empty());
let amx = intrin.amx_intrinsics();
assert!(!amx.is_empty());
}
#[test]
fn test_intrinsics_vnni() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
let vnni_ops = intrin.vnni_int8_intrinsics();
assert!(vnni_ops.len() >= 4);
let desc = intrin.lookup("__builtin_ia32_vpdpbusd").unwrap();
assert_eq!(desc.instr_mnemonic, "vpdpbusd");
assert_eq!(desc.required_feature, MLTargetFeature::AVX512VNNI);
assert_eq!(desc.operands.len(), 3);
}
#[test]
fn test_intrinsics_bf16() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512BF16);
let bf16_ops = intrin.bf16_intrinsics();
assert!(bf16_ops.len() >= 2);
}
#[test]
fn test_intrinsics_count_available() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
let features = vec![MLTargetFeature::AVX512VNNI];
let count = intrin.count_available(&features);
assert!(count > 0);
}
#[test]
fn test_layout_preferences() {
let layout = X86MLLayout::new();
assert_eq!(
layout.preferred_layout(MLOpKind::Conv2D),
MLLayoutFormat::NHWC
);
assert_eq!(
layout.preferred_layout(MLOpKind::MatMul),
MLLayoutFormat::RowMajor
);
assert_eq!(
layout.preferred_layout(MLOpKind::ReLU),
MLLayoutFormat::NHWC
);
assert_eq!(
layout.preferred_layout(MLOpKind::LayerNorm),
MLLayoutFormat::RowMajor
);
}
#[test]
fn test_layout_transform_cost() {
let layout = X86MLLayout::new();
let cost = layout.transform_cost(MLLayoutFormat::NCHW, MLLayoutFormat::NHWC);
assert!(cost > 0.0);
}
#[test]
fn test_layout_block_sizes() {
let layout = X86MLLayout::new();
let l1 = layout.l1_block();
assert_eq!(l1.level, CacheLevel::L1);
assert_eq!(l1.size_bytes, 32 * 1024);
let l2 = layout.l2_block();
assert_eq!(l2.level, CacheLevel::L2);
let l3 = layout.l3_block();
assert_eq!(l3.level, CacheLevel::L3);
}
#[test]
fn test_layout_strides_row_major() {
let shape = vec![2usize, 3, 4];
let strides = X86MLLayout::compute_strides(&shape, MLLayoutFormat::RowMajor);
assert_eq!(strides[2], 1);
assert_eq!(strides[1], 4);
assert_eq!(strides[0], 12);
}
#[test]
fn test_layout_recommend() {
let layout = X86MLLayout::new();
let rec = layout.recommend_layout(
&[MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU],
MLDataType::INT8,
&[MLTargetFeature::AVX512VNNI],
);
assert!(matches!(rec, MLLayoutFormat::VNNIPacked { .. }));
let rec = layout.recommend_layout(
&[MLOpKind::MatMul],
MLDataType::FP32,
&[MLTargetFeature::AVX2],
);
assert_eq!(rec, MLLayoutFormat::RowMajor);
}
#[test]
fn test_graph_fusion_conv_bn_relu() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU]);
assert!(fused.is_some());
let f = fused.unwrap();
assert_eq!(f.name, "ConvBNReLU");
assert_eq!(f.ops.len(), 3);
}
#[test]
fn test_graph_fusion_linear_relu() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::MatMul, MLOpKind::Add, MLOpKind::ReLU]);
assert!(fused.is_some());
assert_eq!(fused.unwrap().name, "LinearReLU");
}
#[test]
fn test_graph_fusion_layernorm_linear() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::LayerNorm, MLOpKind::MatMul]);
assert!(fused.is_some());
assert_eq!(fused.unwrap().name, "NormLinear");
}
#[test]
fn test_graph_fusion_residual_norm() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::Add, MLOpKind::LayerNorm]);
assert!(fused.is_some());
assert_eq!(fused.unwrap().name, "ResidualNorm");
}
#[test]
fn test_graph_fusion_linear_silu() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::MatMul, MLOpKind::SiLU]);
assert!(fused.is_some());
assert_eq!(fused.unwrap().name, "LinearSiLU");
}
#[test]
fn test_graph_fusion_linear_gelu() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let fused = graph.try_fuse(&[MLOpKind::MatMul, MLOpKind::GELU]);
assert!(fused.is_some());
assert_eq!(fused.unwrap().name, "LinearGELU");
}
#[test]
fn test_graph_no_fusion_unknown() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
assert!(graph
.try_fuse(&[MLOpKind::Conv2D, MLOpKind::Conv2D])
.is_none());
}
#[test]
fn test_graph_kernel_selection() {
let graph = X86MLGraph::new(MLTargetFeature::AVX512F, MLOptLevel::O2);
let kernel = graph.select_kernel(
MLOpKind::MatMul,
MLDataType::FP32,
64,
128,
256,
&[MLTargetFeature::AVX512F],
);
assert!(kernel.is_some());
assert!(kernel.unwrap().contains("avx512"));
}
#[test]
fn test_graph_kernel_selection_int8() {
let graph = X86MLGraph::new(MLTargetFeature::AVX512VNNI, MLOptLevel::O2);
let kernel = graph.select_kernel(
MLOpKind::MatMul,
MLDataType::INT8,
64,
128,
256,
&[MLTargetFeature::AVX512VNNI],
);
assert!(kernel.is_some());
assert!(kernel.unwrap().contains("vnni"));
}
#[test]
fn test_graph_memory_planning() {
let mut graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let mlg = MLGraph {
name: "test".into(),
nodes: vec![
MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![64, 256]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
},
MLGraphNode {
id: 1,
op: MLOpKind::ReLU,
inputs: vec![0],
outputs: vec![],
shape: TensorShape::new(vec![64, 256]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
},
],
edges: vec![(0, 1)],
};
let pool = graph.plan_memory(&mlg);
assert!(pool.peak_bytes > 0);
}
#[test]
fn test_graph_auto_tune_config() {
let graph = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let config = graph.auto_tune_config();
assert!(config.enabled);
assert_eq!(config.warmup_iterations, 5);
assert_eq!(config.measure_iterations, 20);
assert!(!config.block_size_candidates.is_empty());
}
#[test]
fn test_quantization_create() {
let quant = X86MLQuantization::new();
assert!(quant.supports_mode(QuantMode::PostTraining));
assert!(quant.supports_mode(QuantMode::QAT));
assert!(quant.supports_mode(QuantMode::Dynamic));
assert!(quant.supports_mode(QuantMode::Static));
}
#[test]
fn test_quantize_fp32_to_int8() {
let quant = X86MLQuantization::new();
let result = quant.quantize(MLOpKind::MatMul, MLDataType::FP32, MLDataType::INT8);
assert!(result.is_ok());
let qop = result.unwrap();
assert_eq!(qop.input_dtype, MLDataType::FP32);
assert_eq!(qop.weight_dtype, MLDataType::INT8);
assert!(qop.input_scale.is_some());
}
#[test]
fn test_quantize_fp32_to_fp16() {
let quant = X86MLQuantization::new();
let result = quant.quantize(MLOpKind::MatMul, MLDataType::FP32, MLDataType::FP16);
assert!(result.is_ok());
}
#[test]
fn test_quantize_invalid() {
let quant = X86MLQuantization::new();
let result = quant.quantize(MLOpKind::MatMul, MLDataType::INT8, MLDataType::FP32);
assert!(result.is_err());
}
#[test]
fn test_quantize_calibration() {
let mut quant = X86MLQuantization::new();
let samples = vec![
vec![-1.0, -0.5, 0.0, 0.5, 1.0],
vec![-2.0, -1.0, 0.0, 1.0, 2.0],
];
quant.calibrate(&samples, 256);
assert!(quant.calibration().is_some());
let scale = quant.compute_scale_minmax(0, MLDataType::INT8);
assert!(scale.is_some());
let (s, zp) = scale.unwrap();
assert!(s > 0.0);
}
#[test]
fn test_runtime_creation() {
let rt = X86MLRuntime::new();
assert!(rt.supports_runtime(MLRuntime::ONNX));
assert!(rt.supports_runtime(MLRuntime::OpenVINO));
assert!(rt.supports_runtime(MLRuntime::TFLiteMicro));
assert!(rt.supports_runtime(MLRuntime::Native));
assert!(rt.supports_runtime(MLRuntime::GGML));
}
#[test]
fn test_runtime_custom_op_registration() {
let mut rt = X86MLRuntime::new();
let op = CustomOperator {
name: "MyCustomOp".into(),
domain: "custom.ai".into(),
version: 1,
op_type: MLOpKind::GELU,
input_types: vec![MLDataType::FP32],
output_types: vec![MLDataType::FP32],
kernel_name: "x86_gelu_opt".into(),
};
rt.register_custom_op(op);
assert_eq!(rt.custom_operators().len(), 1);
}
#[test]
fn test_runtime_onnx_codegen() {
let rt = X86MLRuntime::new();
let op = CustomOperator {
name: "TestOp".into(),
domain: "test".into(),
version: 1,
op_type: MLOpKind::ReLU,
input_types: vec![MLDataType::FP32],
output_types: vec![MLDataType::FP32],
kernel_name: "x86_relu_vec".into(),
};
let code = rt.emit_onnx_custom_op_registration(&op);
assert!(code.contains("onnxruntime"));
assert!(code.contains("TestOp"));
assert!(code.contains("x86_relu_vec"));
}
#[test]
fn test_runtime_openvino_codegen() {
let rt = X86MLRuntime::new();
let op = CustomOperator {
name: "OVOp".into(),
domain: "openvino".into(),
version: 1,
op_type: MLOpKind::Conv2D,
input_types: vec![MLDataType::FP32],
output_types: vec![MLDataType::FP32],
kernel_name: "x86_conv2d_vec".into(),
};
let xml = rt.emit_openvino_custom_layer(&op);
assert!(xml.contains("CustomLayer"));
assert!(xml.contains("OVOp"));
}
#[test]
fn test_runtime_tflite_codegen() {
let rt = X86MLRuntime::new();
let op = CustomOperator {
name: "TFLiteOp".into(),
domain: "tflite".into(),
version: 1,
op_type: MLOpKind::GELU,
input_types: vec![MLDataType::FP32],
output_types: vec![MLDataType::FP32],
kernel_name: "x86_gelu_vec".into(),
};
let code = rt.emit_tflite_micro_registration(&op);
assert!(code.contains("TfLiteRegistration"));
assert!(code.contains("TFLiteOp"));
}
#[test]
fn test_runtime_native_header() {
let rt = X86MLRuntime::new();
let header = rt.emit_native_runtime_header();
assert!(header.contains("X86_ML_NATIVE_RUNTIME_H"));
assert!(header.contains("x86_ml_alloc"));
assert!(header.contains("x86_ml_invoke"));
}
#[test]
fn test_runtime_ggml_mapping() {
let rt = X86MLRuntime::new();
let ops = vec![
MLOpKind::MatMul,
MLOpKind::ReLU,
MLOpKind::GELU,
MLOpKind::SiLU,
];
let mapping = rt.emit_ggml_op_mapping(&ops);
assert!(mapping.contains("ggml_mul_mat"));
assert!(mapping.contains("ggml_relu"));
assert!(mapping.contains("ggml_gelu"));
assert!(mapping.contains("ggml_silu"));
}
#[test]
fn test_integration_full_pipeline() {
let features = vec![
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let r = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![4, 64, 128]),
&TensorShape::new(vec![128, 256]),
&TensorShape::new(vec![4, 64, 256]),
);
assert!(r.is_ok());
let v = ml.vectorize_op(MLOpKind::MatMul, MLDataType::FP32);
assert!(v.is_ok());
let fused = ml.try_fuse_ops(&[MLOpKind::MatMul, MLOpKind::Add, MLOpKind::ReLU]);
assert!(fused.is_some());
let q = ml.quantize_op(MLOpKind::MatMul, MLDataType::FP32, MLDataType::INT8);
assert!(q.is_ok());
}
#[test]
fn test_integration_conv_pipeline() {
let features = vec![MLTargetFeature::AVX512F];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let r = ml.lower_tensor_op(
MLOpKind::Conv2D,
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![128, 64, 3, 3]),
&TensorShape::new(vec![1, 128, 56, 56]),
);
assert!(r.is_ok());
assert!(r.unwrap().vectorized);
}
#[test]
fn test_integration_attention_pipeline() {
let features = vec![MLTargetFeature::AVX512F, MLTargetFeature::AVX512BF16];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let r = ml.lower_tensor_op(
MLOpKind::ScaledDotProductAttention,
&TensorShape::new(vec![1, 8, 128, 64]),
&TensorShape::new(vec![1, 8, 128, 64]),
&TensorShape::new(vec![1, 8, 128, 64]),
);
assert!(r.is_ok());
let r = ml.lower_tensor_op(
MLOpKind::MultiHeadAttention,
&TensorShape::new(vec![128, 512]),
&TensorShape::new(vec![128, 512]),
&TensorShape::new(vec![128, 512]),
);
assert!(r.is_ok());
}
#[test]
fn test_edge_case_empty_shape() {
let features = vec![MLTargetFeature::SSE2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O0);
let r = ml.lower_tensor_op(
MLOpKind::Add,
&TensorShape::new(vec![]),
&TensorShape::new(vec![]),
&TensorShape::new(vec![]),
);
assert!(r.is_ok());
}
#[test]
fn test_edge_case_large_shape() {
let features = vec![MLTargetFeature::AVX512F];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let r = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![1024, 4096]),
&TensorShape::new(vec![4096, 1024]),
&TensorShape::new(vec![1024, 1024]),
);
assert!(r.is_ok());
let result = r.unwrap();
assert!(result.flops > 1_000_000_000); }
#[test]
fn test_edge_case_all_features() {
let all_features = vec![
MLTargetFeature::SSE2,
MLTargetFeature::AVX,
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
MLTargetFeature::AVX512BF16,
MLTargetFeature::AVX512FP16,
MLTargetFeature::AMXINT8,
MLTargetFeature::AMXBF16,
MLTargetFeature::AMXFP16,
];
let ml = X86MLSupport::new(all_features.clone(), MLOptLevel::O3);
assert!(ml.has_feature(MLTargetFeature::AVX2));
assert!(ml.has_feature(MLTargetFeature::AVX512VNNI));
assert!(ml.has_feature(MLTargetFeature::AMXINT8));
assert_eq!(ml.max_feature(), MLTargetFeature::AMXFP16);
}
#[test]
fn test_edge_case_intrinsic_decl() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
let desc = intrin.lookup("__builtin_ia32_vpdpbusd").unwrap();
let decl = intrin.emit_decl(desc);
assert!(decl.contains("__builtin_ia32_vpdpbusd"));
assert!(decl.contains("__m512i"));
}
#[test]
fn test_edge_case_header_emission() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AMXINT8);
let header = intrin.emit_header();
assert!(header.contains("_X86_ML_INTRINSICS_H_"));
assert!(header.contains("immintrin.h"));
assert!(header.contains("VNNI Intrinsics"));
assert!(header.contains("BF16 Intrinsics"));
assert!(header.contains("AMX Intrinsics"));
}
}
impl MLGraph {
pub fn new(name: &str) -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
name: name.to_string(),
}
}
pub fn add_node(&mut self, node: MLGraphNode) -> usize {
let id = self.nodes.len();
self.nodes.push(node);
id
}
pub fn add_edge(&mut self, from: usize, to: usize) {
self.edges.push((from, to));
}
pub fn topological_order(&self) -> Vec<usize> {
let mut in_degree = vec![0usize; self.nodes.len()];
for &(from, to) in &self.edges {
in_degree[to] += 1;
}
let mut queue: Vec<usize> = in_degree
.iter()
.enumerate()
.filter(|(_, &d)| d == 0)
.map(|(i, _)| i)
.collect();
let mut order = Vec::new();
while let Some(node) = queue.pop() {
order.push(node);
for &(f, t) in &self.edges {
if f == node {
in_degree[t] -= 1;
if in_degree[t] == 0 {
queue.push(t);
}
}
}
}
if order.len() == self.nodes.len() {
order
} else {
(0..self.nodes.len()).collect()
}
}
pub fn input_nodes(&self) -> Vec<usize> {
let has_incoming: HashSet<usize> = self.edges.iter().map(|(_, to)| *to).collect();
(0..self.nodes.len())
.filter(|i| !has_incoming.contains(i))
.collect()
}
pub fn output_nodes(&self) -> Vec<usize> {
let has_outgoing: HashSet<usize> = self.edges.iter().map(|(from, _)| *from).collect();
(0..self.nodes.len())
.filter(|i| !has_outgoing.contains(i))
.collect()
}
pub fn op_counts(&self) -> HashMap<MLOpKind, usize> {
let mut counts = HashMap::new();
for node in &self.nodes {
*counts.entry(node.op).or_insert(0) += 1;
}
counts
}
pub fn estimate_flops(&self) -> u64 {
self.nodes
.iter()
.map(|n| {
let numel = n.shape.numel();
match n.op {
MLOpKind::MatMul => {
let m = n.shape.dim(0).unwrap_or(1);
let k = n.shape.dim(1).unwrap_or(1);
(2 * m * k * numel) as u64
}
MLOpKind::Conv2D => (numel * 9) as u64,
MLOpKind::Softmax => (numel * 4) as u64,
MLOpKind::LayerNorm => (numel * 5) as u64,
MLOpKind::GELU | MLOpKind::SiLU => (numel * 3) as u64,
_ => numel as u64,
}
})
.sum()
}
}
impl X86MLGraph {
pub fn analyze(&self, graph: &MLGraph) -> GraphAnalysis {
let fusions = self.find_fusions(graph);
let op_counts = graph.op_counts();
let total_flops = graph.estimate_flops();
let total_params = graph.nodes.iter().map(|n| n.shape.numel()).sum::<usize>();
let mut recommendations = Vec::new();
if !fusions.is_empty() {
recommendations.push(format!(
"{} operator fusion(s) available — estimated {:.1}% speedup",
fusions.len(),
fusions.iter().map(|f| f.estimated_speedup).sum::<f64>() * 10.0
));
}
let matmul_count = op_counts.get(&MLOpKind::MatMul).copied().unwrap_or(0);
let conv_count = op_counts.get(&MLOpKind::Conv2D).copied().unwrap_or(0);
if matmul_count + conv_count > 5 {
recommendations.push(format!(
"{} matmul/conv ops detected — consider INT8 quantization for 2-4× throughput",
matmul_count + conv_count
));
}
if conv_count > 0 {
recommendations.push("NHWC layout recommended for convolution ops on X86".into());
}
GraphAnalysis {
node_count: graph.nodes.len(),
edge_count: graph.edges.len(),
total_flops,
total_params,
op_counts,
fusion_opportunities: fusions.len(),
recommendations,
}
}
}
#[derive(Debug, Clone)]
pub struct GraphAnalysis {
pub node_count: usize,
pub edge_count: usize,
pub total_flops: u64,
pub total_params: usize,
pub op_counts: HashMap<MLOpKind, usize>,
pub fusion_opportunities: usize,
pub recommendations: Vec<String>,
}
pub struct X86MLScheduler {
pub num_cores: usize,
pub simd_width: usize,
pub cache_info: CacheHierarchy,
pub schedule: Vec<ScheduledOp>,
}
#[derive(Debug, Clone)]
pub struct CacheHierarchy {
pub l1_size: usize,
pub l2_size: usize,
pub l3_size: usize,
pub l1_line_size: usize,
pub l2_line_size: usize,
pub l3_line_size: usize,
}
impl Default for CacheHierarchy {
fn default() -> Self {
Self {
l1_size: 32 * 1024,
l2_size: 256 * 1024,
l3_size: 8 * 1024 * 1024,
l1_line_size: 64,
l2_line_size: 64,
l3_line_size: 64,
}
}
}
#[derive(Debug, Clone)]
pub struct ScheduledOp {
pub op: MLOpKind,
pub core: usize,
pub start_cycle: u64,
pub end_cycle: u64,
pub block_m: usize,
pub block_n: usize,
pub block_k: usize,
pub isa: MLTargetFeature,
}
impl X86MLScheduler {
pub fn new(num_cores: usize, max_feature: MLTargetFeature) -> Self {
let simd_width = match max_feature {
MLTargetFeature::AVX512F
| MLTargetFeature::AVX512VNNI
| MLTargetFeature::AVX512BF16
| MLTargetFeature::AVX512FP16 => 16,
MLTargetFeature::AVX2 | MLTargetFeature::AVX => 8,
_ => 4,
};
Self {
num_cores,
simd_width,
cache_info: CacheHierarchy::default(),
schedule: Vec::new(),
}
}
pub fn schedule_ops(&mut self, ops: &[(MLOpKind, usize, usize, usize)]) {
self.schedule.clear();
let mut core_available: Vec<u64> = vec![0u64; self.num_cores];
for &(op, m, n, k) in ops {
let (core, start) = core_available
.iter()
.enumerate()
.min_by_key(|(_, &t)| t)
.map(|(c, &t)| (c, t))
.unwrap_or((0, 0));
let cycles = self.estimate_cycles(op, m, n, k);
core_available[core] = start + cycles;
self.schedule.push(ScheduledOp {
op,
core,
start_cycle: start,
end_cycle: start + cycles,
block_m: m,
block_n: n,
block_k: k,
isa: MLTargetFeature::AVX2,
});
}
}
pub fn estimate_cycles(&self, op: MLOpKind, m: usize, n: usize, k: usize) -> u64 {
let flops = match op {
MLOpKind::MatMul => 2u64 * m as u64 * n as u64 * k as u64,
MLOpKind::Conv2D => 2u64 * m as u64 * n as u64 * k as u64 * 9u64,
MLOpKind::ReLU | MLOpKind::GELU | MLOpKind::SiLU => (m * n * k) as u64,
_ => (m * n * k) as u64,
};
let flops_per_cycle = self.simd_width as u64 * 2; flops / flops_per_cycle
}
pub fn makespan(&self) -> u64 {
self.schedule.iter().map(|s| s.end_cycle).max().unwrap_or(0)
}
pub fn core_utilization(&self) -> Vec<f64> {
let makespan = self.makespan();
if makespan == 0 {
return vec![0.0; self.num_cores];
}
let mut total_cycles = vec![0u64; self.num_cores];
for sched in &self.schedule {
total_cycles[sched.core] += sched.end_cycle - sched.start_cycle;
}
total_cycles
.iter()
.map(|&c| c as f64 / makespan as f64)
.collect()
}
pub fn current_schedule(&self) -> &[ScheduledOp] {
&self.schedule
}
}
pub struct X86MLProfiler {
pub results: HashMap<String, Vec<ProfileEntry>>,
pub enabled: bool,
pub session: Option<ProfileSession>,
}
#[derive(Debug, Clone)]
pub struct ProfileEntry {
pub op_name: String,
pub kernel_name: String,
pub duration_us: f64,
pub flops: u64,
pub bytes_read: u64,
pub bytes_written: u64,
pub gflops_per_sec: f64,
pub bandwidth_gb_per_sec: f64,
pub cache_misses_l1: Option<u64>,
pub cache_misses_l2: Option<u64>,
pub cache_misses_l3: Option<u64>,
pub timestamp: u64,
}
#[derive(Debug, Clone)]
pub struct ProfileSession {
pub name: String,
pub start_time: std::time::Instant,
pub entries: Vec<ProfileEntry>,
}
impl X86MLProfiler {
pub fn new() -> Self {
Self {
results: HashMap::new(),
enabled: false,
session: None,
}
}
pub fn start_session(&mut self, name: &str) {
self.enabled = true;
self.session = Some(ProfileSession {
name: name.to_string(),
start_time: std::time::Instant::now(),
entries: Vec::new(),
});
}
pub fn record(
&mut self,
op_name: &str,
kernel_name: &str,
duration_us: f64,
flops: u64,
bytes_read: u64,
bytes_written: u64,
) {
if !self.enabled {
return;
}
let gflops = if duration_us > 0.0 {
flops as f64 / (duration_us * 1e-3) / 1e9
} else {
0.0
};
let bw = if duration_us > 0.0 {
((bytes_read + bytes_written) as f64) / (duration_us * 1e-6) / 1e9
} else {
0.0
};
let entry = ProfileEntry {
op_name: op_name.to_string(),
kernel_name: kernel_name.to_string(),
duration_us,
flops,
bytes_read,
bytes_written,
gflops_per_sec: gflops,
bandwidth_gb_per_sec: bw,
cache_misses_l1: None,
cache_misses_l2: None,
cache_misses_l3: None,
timestamp: 0,
};
if let Some(ref mut session) = self.session {
session.entries.push(entry);
}
}
pub fn end_session(&mut self) -> Option<Vec<ProfileEntry>> {
self.enabled = false;
if let Some(session) = self.session.take() {
self.results
.insert(session.name.clone(), session.entries.clone());
Some(session.entries)
} else {
None
}
}
pub fn report(&self) -> String {
let mut out = String::from("╔══════════════════════════════════════════════════╗\n");
out.push_str("║ X86 ML Profile Report ║\n");
out.push_str("╠══════════════════════════════════════════════════╣\n");
for (session_name, entries) in &self.results {
out.push_str(&format!("║ Session: {:<40} ║\n", session_name));
out.push_str("╠══════════════════════════════════════════════════╣\n");
out.push_str("║ Op Kernel Time(us) GFLOPS BW(GB/s) ║\n");
for entry in entries {
out.push_str(&format!(
"║ {:<12} {:<14} {:>8.1} {:>7.1} {:>9.1} ║\n",
&entry.op_name[..entry.op_name.len().min(12)],
&entry.kernel_name[..entry.kernel_name.len().min(14)],
entry.duration_us,
entry.gflops_per_sec,
entry.bandwidth_gb_per_sec,
));
}
out.push_str("╚══════════════════════════════════════════════════╝\n\n");
}
if self.results.is_empty() {
out.push_str("║ (No profiling data collected) ║\n");
out.push_str("╚══════════════════════════════════════════════════╝\n");
}
out
}
pub fn all_results(&self) -> &HashMap<String, Vec<ProfileEntry>> {
&self.results
}
pub fn clear(&mut self) {
self.results.clear();
self.session = None;
}
}
impl Default for X86MLProfiler {
fn default() -> Self {
Self::new()
}
}
pub struct X86MLCodegen {
pub feature: MLTargetFeature,
pub code_buffer: Vec<String>,
label_counter: usize,
reg_alloc: RegAllocState,
}
#[derive(Debug, Clone)]
struct RegAllocState {
pub gprs: Vec<String>,
pub simd: Vec<String>,
pub next_stack_offset: usize,
}
impl Default for RegAllocState {
fn default() -> Self {
Self {
gprs: vec![
"%rax".into(),
"%rbx".into(),
"%rcx".into(),
"%rdx".into(),
"%rsi".into(),
"%rdi".into(),
"%r8".into(),
"%r9".into(),
"%r10".into(),
"%r11".into(),
"%r12".into(),
"%r13".into(),
"%r14".into(),
"%r15".into(),
],
simd: vec![
"%xmm0".into(),
"%xmm1".into(),
"%xmm2".into(),
"%xmm3".into(),
"%xmm4".into(),
"%xmm5".into(),
"%xmm6".into(),
"%xmm7".into(),
"%xmm8".into(),
"%xmm9".into(),
"%xmm10".into(),
"%xmm11".into(),
"%xmm12".into(),
"%xmm13".into(),
"%xmm14".into(),
"%xmm15".into(),
],
next_stack_offset: 0,
}
}
}
impl X86MLCodegen {
pub fn new(feature: MLTargetFeature) -> Self {
Self {
feature,
code_buffer: Vec::new(),
label_counter: 0,
reg_alloc: RegAllocState::default(),
}
}
pub fn unique_label(&mut self, prefix: &str) -> String {
let label = format!(".L_{}_{}", prefix, self.label_counter);
self.label_counter += 1;
label
}
pub fn emit(&mut self, line: &str) {
self.code_buffer.push(line.to_string());
}
pub fn emit_label(&mut self, label: &str) {
self.code_buffer.push(format!("{}:", label));
}
pub fn emit_comment(&mut self, comment: &str) {
self.code_buffer.push(format!("# {}", comment));
}
pub fn emit_prologue(&mut self, name: &str) {
self.emit_comment(&format!("Function: {}", name));
self.emit(&format!(".globl {}", name));
self.emit(&format!(".type {}, @function", name));
self.emit_label(name);
self.emit("pushq %rbp");
self.emit("movq %rsp, %rbp");
}
pub fn emit_epilogue(&mut self) {
self.emit("popq %rbp");
self.emit("ret");
}
pub fn emit_gemm_kernel(
&mut self,
name: &str,
m: usize,
n: usize,
k: usize,
dtype: MLDataType,
) {
self.emit_prologue(name);
let reg = match self.feature {
MLTargetFeature::AVX512F
| MLTargetFeature::AVX512VNNI
| MLTargetFeature::AVX512BF16
| MLTargetFeature::AVX512FP16 => "zmm",
MLTargetFeature::AVX2 | MLTargetFeature::AVX => "ymm",
_ => "xmm",
};
let vec_bytes = match reg {
"zmm" => 64,
"ymm" => 32,
_ => 16,
};
let k_blocks = (k + GEMM_BLOCK_K - 1) / GEMM_BLOCK_K;
let n_blocks = (n + GEMM_BLOCK_N - 1) / GEMM_BLOCK_N;
let m_blocks = (m + GEMM_BLOCK_M - 1) / GEMM_BLOCK_M;
self.emit_comment(&format!(
"GEMM: M={} N={} K={} dtype={:?} isa={}",
m,
n,
k,
dtype,
self.feature.isa_level()
));
self.emit_comment(&format!(
"Blocking: M×N×K = {}×{}×{}, blocks: {}×{}×{}",
GEMM_BLOCK_M, GEMM_BLOCK_N, GEMM_BLOCK_K, m_blocks, n_blocks, k_blocks
));
let loop_m = self.unique_label("gemm_loop_m");
let loop_n = self.unique_label("gemm_loop_n");
let loop_k = self.unique_label("gemm_loop_k");
let end_m = self.unique_label("gemm_end_m");
let end_n = self.unique_label("gemm_end_n");
let end_k = self.unique_label("gemm_end_k");
self.emit(&format!("vxorps %{}, %{}, %{}", reg, reg, reg));
self.emit("xorl %eax, %eax");
self.emit_label(&loop_m);
self.emit(&format!("cmpq ${}, %rax", m_blocks * GEMM_BLOCK_M));
self.emit(&format!("jge {}", end_m));
self.emit("xorl %ebx, %ebx");
self.emit_label(&loop_n);
self.emit(&format!("cmpq ${}, %rbx", n_blocks * GEMM_BLOCK_N));
self.emit(&format!("jge {}", end_n));
self.emit("xorl %ecx, %ecx");
self.emit_label(&loop_k);
self.emit(&format!("cmpq ${}, %rcx", k_blocks * GEMM_BLOCK_K));
self.emit(&format!("jge {}", end_k));
self.emit(&format!("vmovups ({}mm0), [%rsi + %rax + %rcx]", reg));
self.emit(&format!("vmovups ({}mm1), [%rdx + %rcx + %rbx]", reg));
match dtype {
MLDataType::INT8 if self.feature == MLTargetFeature::AVX512VNNI => {
self.emit(&format!("vpdpbusd {}mm2, {}mm0, {}mm1", reg, reg, reg));
}
MLDataType::BF16 if self.feature == MLTargetFeature::AVX512BF16 => {
self.emit(&format!("vdpbf16ps {}mm2, {}mm0, {}mm1", reg, reg, reg));
}
_ => {
self.emit(&format!("vfmadd231ps {}mm2, {}mm0, {}mm1", reg, reg, reg));
}
}
self.emit(&format!("addq ${}, %rcx", vec_bytes));
self.emit(&format!("jmp {}", loop_k));
self.emit_label(&end_k);
self.emit(&format!("vmovups [%rdi + %rax + %rbx], ({}mm2)", reg));
self.emit(&format!(
"addq ${}, %rbx",
GEMM_BLOCK_N * dtype.size_bytes()
));
self.emit(&format!("jmp {}", loop_n));
self.emit_label(&end_n);
self.emit(&format!(
"addq ${}, %rax",
GEMM_BLOCK_M * k * dtype.size_bytes()
));
self.emit(&format!("jmp {}", loop_m));
self.emit_label(&end_m);
self.emit_epilogue();
}
pub fn emit_relu_kernel(&mut self, name: &str, num_elements: usize) {
self.emit_prologue(name);
self.emit_comment(&format!("ReLU: {} elements", num_elements));
let reg = match self.feature {
MLTargetFeature::AVX512F
| MLTargetFeature::AVX512VNNI
| MLTargetFeature::AVX512BF16
| MLTargetFeature::AVX512FP16 => "zmm",
MLTargetFeature::AVX2 | MLTargetFeature::AVX => "ymm",
_ => "xmm",
};
let lanes = match reg {
"zmm" => 16,
"ymm" => 8,
_ => 4,
};
let loop_label = self.unique_label("relu_loop");
let end_label = self.unique_label("relu_end");
self.emit("xorl %ecx, %ecx");
self.emit(&format!("vxorps %{}, %{}, %{}", reg, reg, reg)); self.emit_label(&loop_label);
self.emit(&format!("cmpq ${}, %rcx", num_elements));
self.emit(&format!("jge {}", end_label));
self.emit(&format!("vmovups ({}mm1), [%rsi + %rcx*4]", reg));
self.emit(&format!("vmaxps {}mm1, {}mm1, {}mm0", reg, reg, reg));
self.emit(&format!("vmovups [%rdi + %rcx*4], ({}mm1)", reg));
self.emit(&format!("addq ${}, %rcx", lanes));
self.emit(&format!("jmp {}", loop_label));
self.emit_label(&end_label);
self.emit_epilogue();
}
pub fn emit_layernorm_kernel(&mut self, name: &str, dim: usize) {
self.emit_prologue(name);
self.emit_comment(&format!("LayerNorm: dim={}", dim));
let reg = match self.feature {
MLTargetFeature::AVX512F | MLTargetFeature::AVX512VNNI => "zmm",
MLTargetFeature::AVX2 | MLTargetFeature::AVX => "ymm",
_ => "xmm",
};
let lanes = match reg {
"zmm" => 16,
"ymm" => 8,
_ => 4,
};
self.emit(&format!("vxorps %{}0, %{}0, %{}0", reg, reg, reg));
let mean_loop = self.unique_label("ln_mean");
let mean_end = self.unique_label("ln_mean_end");
self.emit("xorl %ecx, %ecx");
self.emit_label(&mean_loop);
self.emit(&format!("cmpq ${}, %rcx", dim));
self.emit(&format!("jge {}", mean_end));
self.emit(&format!("vaddps %{}0, %{}0, [%rsi + %rcx*4]", reg, reg));
self.emit(&format!("addq ${}, %rcx", lanes));
self.emit(&format!("jmp {}", mean_loop));
self.emit_label(&mean_end);
self.emit(&format!("vmovups ({}mm2), [%rdx]", reg)); self.emit(&format!("vmovups ({}mm3), [%r8]", reg));
let norm_loop = self.unique_label("ln_norm");
let norm_end = self.unique_label("ln_norm_end");
self.emit("xorl %ecx, %ecx");
self.emit_label(&norm_loop);
self.emit(&format!("cmpq ${}, %rcx", dim));
self.emit(&format!("jge {}", norm_end));
self.emit(&format!(
"vfmadd213ps ({}mm0), ({}mm0), ({}mm2), ({}mm3)",
reg, reg, reg, reg
));
self.emit(&format!("vmovups [%rdi + %rcx*4], ({}mm0)", reg));
self.emit(&format!("addq ${}, %rcx", lanes));
self.emit(&format!("jmp {}", norm_loop));
self.emit_label(&norm_end);
self.emit_epilogue();
}
pub fn emit_softmax_kernel(&mut self, name: &str, dim: usize) {
self.emit_prologue(name);
self.emit_comment(&format!("Softmax: dim={}", dim));
let reg = match self.feature {
MLTargetFeature::AVX512F | MLTargetFeature::AVX512VNNI => "zmm",
MLTargetFeature::AVX2 | MLTargetFeature::AVX => "ymm",
_ => "xmm",
};
self.emit(&format!("vmovups ({}mm0), [%rsi]", reg));
let max_loop = self.unique_label("sm_max");
let max_end = self.unique_label("sm_max_end");
self.emit("xorl %ecx, %ecx");
self.emit_label(&max_loop);
self.emit(&format!("cmpq ${}, %rcx", dim));
self.emit(&format!("jge {}", max_end));
self.emit(&format!("vmaxps %{}0, %{}0, [%rsi + %rcx*4]", reg, reg));
self.emit(&format!("addq $16, %rcx"));
self.emit(&format!("jmp {}", max_loop));
self.emit_label(&max_end);
self.emit(&format!("vextractf128 $1, %ymm0, %xmm1"));
self.emit(&format!("vmaxps %xmm0, %xmm0, %xmm1"));
self.emit_comment("exp(x - max) and normalize");
self.emit_epilogue();
}
pub fn dump(&self) -> String {
self.code_buffer.join("\n")
}
pub fn clear(&mut self) {
self.code_buffer.clear();
self.label_counter = 0;
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_scheduler_basic() {
let mut scheduler = X86MLScheduler::new(4, MLTargetFeature::AVX2);
let ops = vec![
(MLOpKind::MatMul, 64, 128, 256),
(MLOpKind::MatMul, 64, 128, 256),
(MLOpKind::ReLU, 64, 128, 1),
(MLOpKind::GELU, 64, 128, 1),
];
scheduler.schedule_ops(&ops);
assert_eq!(scheduler.current_schedule().len(), 4);
assert!(scheduler.makespan() > 0);
}
#[test]
fn test_scheduler_utilization() {
let mut scheduler = X86MLScheduler::new(8, MLTargetFeature::AVX512F);
let mut ops = Vec::new();
for _ in 0..32 {
ops.push((MLOpKind::MatMul, 128, 256, 512));
}
scheduler.schedule_ops(&ops);
let util = scheduler.core_utilization();
assert_eq!(util.len(), 8);
let active_cores = util.iter().filter(|&&u| u > 0.0).count();
assert!(active_cores >= 4);
}
#[test]
fn test_scheduler_cycle_estimation() {
let scheduler = X86MLScheduler::new(1, MLTargetFeature::AVX2);
let cycles = scheduler.estimate_cycles(MLOpKind::MatMul, 64, 64, 64);
assert!(cycles > 10000);
assert!(cycles < 100000);
}
#[test]
fn test_profiler_session() {
let mut profiler = X86MLProfiler::new();
profiler.start_session("test_session");
profiler.record("MatMul", "x86_gemm_avx2", 150.0, 1_000_000, 512, 256);
profiler.record("ReLU", "x86_relu_vec", 5.0, 1000, 256, 256);
let entries = profiler.end_session();
assert!(entries.is_some());
assert_eq!(entries.unwrap().len(), 2);
}
#[test]
fn test_profiler_report() {
let mut profiler = X86MLProfiler::new();
profiler.start_session("perf_test");
profiler.record("GEMM", "gemm_avx512", 250.0, 10_000_000, 4096, 2048);
profiler.end_session();
let report = profiler.report();
assert!(report.contains("GEMM"));
assert!(report.contains("perf_test"));
}
#[test]
fn test_profiler_disabled() {
let mut profiler = X86MLProfiler::new();
profiler.record("Op", "kernel", 1.0, 100, 64, 32);
assert!(profiler.all_results().is_empty());
}
#[test]
fn test_profiler_clear() {
let mut profiler = X86MLProfiler::new();
profiler.start_session("s");
profiler.record("a", "b", 1.0, 10, 8, 4);
profiler.end_session();
assert!(!profiler.all_results().is_empty());
profiler.clear();
assert!(profiler.all_results().is_empty());
}
#[test]
fn test_codegen_gemm_kernel() {
let mut cg = X86MLCodegen::new(MLTargetFeature::AVX2);
cg.emit_gemm_kernel("test_gemm", 64, 128, 256, MLDataType::FP32);
let code = cg.dump();
assert!(code.contains("test_gemm"));
assert!(code.contains("vfmadd"));
assert!(code.contains("pushq %rbp"));
assert!(code.contains("ret"));
}
#[test]
fn test_codegen_gemm_vnni() {
let mut cg = X86MLCodegen::new(MLTargetFeature::AVX512VNNI);
cg.emit_gemm_kernel("test_gemm_int8", 64, 128, 256, MLDataType::INT8);
let code = cg.dump();
assert!(code.contains("vpdpbusd"));
assert!(code.contains("zmm"));
}
#[test]
fn test_codegen_relu_kernel() {
let mut cg = X86MLCodegen::new(MLTargetFeature::AVX512F);
cg.emit_relu_kernel("test_relu", 1024);
let code = cg.dump();
assert!(code.contains("test_relu"));
assert!(code.contains("vmaxps"));
}
#[test]
fn test_codegen_layernorm_kernel() {
let mut cg = X86MLCodegen::new(MLTargetFeature::AVX2);
cg.emit_layernorm_kernel("test_ln", 768);
let code = cg.dump();
assert!(code.contains("test_ln"));
assert!(code.contains("LayerNorm"));
}
#[test]
fn test_codegen_softmax_kernel() {
let mut cg = X86MLCodegen::new(MLTargetFeature::AVX2);
cg.emit_softmax_kernel("test_softmax", 256);
let code = cg.dump();
assert!(code.contains("test_softmax"));
assert!(code.contains("Softmax"));
}
#[test]
fn test_codegen_unique_labels() {
let mut cg = X86MLCodegen::new(MLTargetFeature::SSE2);
let l1 = cg.unique_label("test");
let l2 = cg.unique_label("test");
assert_ne!(l1, l2);
}
#[test]
fn test_graph_creation() {
let mut graph = MLGraph::new("test_graph");
let n0 = graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![64, 128]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
assert_eq!(n0, 0);
assert_eq!(graph.nodes.len(), 1);
}
#[test]
fn test_graph_topological_order() {
let mut graph = MLGraph::new("dag");
graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![64, 128]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 1,
op: MLOpKind::ReLU,
inputs: vec![0],
outputs: vec![2],
shape: TensorShape::new(vec![64, 128]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 2,
op: MLOpKind::GELU,
inputs: vec![1],
outputs: vec![],
shape: TensorShape::new(vec![64, 128]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_edge(0, 1);
graph.add_edge(1, 2);
let order = graph.topological_order();
assert_eq!(order.len(), 3);
assert!(
order.iter().position(|&n| n == 0).unwrap()
< order.iter().position(|&n| n == 2).unwrap()
);
}
#[test]
fn test_graph_input_output_nodes() {
let mut graph = MLGraph::new("io_test");
graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 1,
op: MLOpKind::ReLU,
inputs: vec![0],
outputs: vec![2],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 2,
op: MLOpKind::Add,
inputs: vec![1],
outputs: vec![],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_edge(0, 1);
graph.add_edge(1, 2);
let inputs = graph.input_nodes();
let outputs = graph.output_nodes();
assert_eq!(inputs, vec![0]);
assert_eq!(outputs, vec![2]);
}
#[test]
fn test_graph_op_counts() {
let mut graph = MLGraph::new("counts");
graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 1,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 2,
op: MLOpKind::ReLU,
inputs: vec![],
outputs: vec![],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
let counts = graph.op_counts();
assert_eq!(counts.get(&MLOpKind::MatMul), Some(&2));
assert_eq!(counts.get(&MLOpKind::ReLU), Some(&1));
}
#[test]
fn test_graph_analysis() {
let mut graph = MLGraph::new("analysis_test");
graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::Conv2D,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![1, 64, 56, 56]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 1,
op: MLOpKind::BatchNorm,
inputs: vec![0],
outputs: vec![2],
shape: TensorShape::new(vec![1, 64, 56, 56]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 2,
op: MLOpKind::ReLU,
inputs: vec![1],
outputs: vec![],
shape: TensorShape::new(vec![1, 64, 56, 56]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_edge(0, 1);
graph.add_edge(1, 2);
let graph_mgr = X86MLGraph::new(MLTargetFeature::AVX2, MLOptLevel::O2);
let analysis = graph_mgr.analyze(&graph);
assert_eq!(analysis.node_count, 3);
assert_eq!(analysis.edge_count, 2);
assert!(analysis.fusion_opportunities > 0);
assert!(!analysis.recommendations.is_empty());
}
#[test]
fn test_e2e_simple_mlp() {
let features = vec![MLTargetFeature::AVX2, MLTargetFeature::AVX512F];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let l1 = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![1, 784]),
&TensorShape::new(vec![784, 256]),
&TensorShape::new(vec![1, 256]),
);
assert!(l1.is_ok());
let a1 = ml.lower_tensor_op(
MLOpKind::ReLU,
&TensorShape::new(vec![1, 256]),
&TensorShape::new(vec![1, 256]),
&TensorShape::new(vec![1, 256]),
);
assert!(a1.is_ok());
let l2 = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![1, 256]),
&TensorShape::new(vec![256, 10]),
&TensorShape::new(vec![1, 10]),
);
assert!(l2.is_ok());
let s = ml.lower_tensor_op(
MLOpKind::Softmax,
&TensorShape::new(vec![1, 10]),
&TensorShape::new(vec![1, 10]),
&TensorShape::new(vec![1, 10]),
);
assert!(s.is_ok());
}
#[test]
fn test_e2e_transformer_block() {
let features = vec![MLTargetFeature::AVX512F, MLTargetFeature::AVX512BF16];
let mut ml = X86MLSupport::new(features, MLOptLevel::O3);
let hidden = 768;
let seq = 128;
let ln1 = ml.lower_tensor_op(
MLOpKind::LayerNorm,
&TensorShape::new(vec![seq, hidden]),
&TensorShape::new(vec![seq, hidden]),
&TensorShape::new(vec![seq, hidden]),
);
assert!(ln1.is_ok());
let qkv = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![seq, hidden]),
&TensorShape::new(vec![hidden, hidden * 3]),
&TensorShape::new(vec![seq, hidden * 3]),
);
assert!(qkv.is_ok());
let attn = ml.lower_tensor_op(
MLOpKind::ScaledDotProductAttention,
&TensorShape::new(vec![1, 12, seq, 64]),
&TensorShape::new(vec![1, 12, seq, 64]),
&TensorShape::new(vec![1, 12, seq, 64]),
);
assert!(attn.is_ok());
let out = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![seq, hidden]),
&TensorShape::new(vec![hidden, hidden]),
&TensorShape::new(vec![seq, hidden]),
);
assert!(out.is_ok());
let ffn = ml.lower_tensor_op(
MLOpKind::MatMul,
&TensorShape::new(vec![seq, hidden]),
&TensorShape::new(vec![hidden, hidden * 4]),
&TensorShape::new(vec![seq, hidden * 4]),
);
assert!(ffn.is_ok());
let gelu = ml.lower_tensor_op(
MLOpKind::GELU,
&TensorShape::new(vec![seq, hidden * 4]),
&TensorShape::new(vec![seq, hidden * 4]),
&TensorShape::new(vec![seq, hidden * 4]),
);
assert!(gelu.is_ok());
}
#[test]
fn test_e2e_llama_style() {
let features = vec![MLTargetFeature::AVX512F, MLTargetFeature::AVX512VNNI];
let mut ml = X86MLSupport::new(features, MLOptLevel::O3);
let dim = 4096;
let rms = ml.lower_tensor_op(
MLOpKind::RMSNorm,
&TensorShape::new(vec![1, dim]),
&TensorShape::new(vec![1, dim]),
&TensorShape::new(vec![1, dim]),
);
assert!(rms.is_ok());
let silu = ml.lower_tensor_op(
MLOpKind::SiLU,
&TensorShape::new(vec![1, dim]),
&TensorShape::new(vec![1, dim]),
&TensorShape::new(vec![1, dim]),
);
assert!(silu.is_ok());
let q = ml.quantize_op(MLOpKind::MatMul, MLDataType::FP32, MLDataType::INT8);
assert!(q.is_ok());
}
#[test]
fn test_e2e_convnet_resnet_block() {
let features = vec![MLTargetFeature::AVX512F];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
let conv1 = ml.lower_tensor_op(
MLOpKind::Conv2D,
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![64, 64, 3, 3]),
&TensorShape::new(vec![1, 64, 56, 56]),
);
assert!(conv1.is_ok());
let bn1 = ml.lower_tensor_op(
MLOpKind::BatchNorm,
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![1, 64, 56, 56]),
);
assert!(bn1.is_ok());
let relu = ml.lower_tensor_op(
MLOpKind::ReLU,
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![1, 64, 56, 56]),
&TensorShape::new(vec![1, 64, 56, 56]),
);
assert!(relu.is_ok());
let fused = ml.try_fuse_ops(&[MLOpKind::Conv2D, MLOpKind::BatchNorm, MLOpKind::ReLU]);
assert!(fused.is_some());
}
#[test]
fn test_type_conversion_all_pairs() {
let dtypes = X86MLDataType::new();
let pairs = vec![
(MLDataType::FP32, MLDataType::FP16),
(MLDataType::FP16, MLDataType::FP32),
(MLDataType::FP32, MLDataType::BF16),
(MLDataType::BF16, MLDataType::FP32),
(MLDataType::FP32, MLDataType::INT8),
(MLDataType::INT8, MLDataType::FP32),
(MLDataType::FP16, MLDataType::BF16),
(MLDataType::BF16, MLDataType::FP16),
(MLDataType::INT8, MLDataType::INT4),
];
for (from, to) in &pairs {
assert!(
dtypes.can_convert(*from, *to),
"Should support {} → {} conversion",
from,
to
);
let conv = dtypes.get_conversion(*from, *to);
assert!(conv.is_some(), "Missing conversion for {} → {}", from, to);
assert!(!conv.unwrap().instr_sequence.is_empty());
}
}
#[test]
fn test_type_conversion_throughput() {
let dtypes = X86MLDataType::new();
let bf16_conv = dtypes
.get_conversion(MLDataType::FP32, MLDataType::BF16)
.unwrap();
let fp16_conv = dtypes
.get_conversion(MLDataType::FP32, MLDataType::FP16)
.unwrap();
assert!(bf16_conv.throughput_hint >= fp16_conv.throughput_hint);
}
#[test]
fn test_all_ops_have_names() {
let all_ops = [
MLOpKind::MatMul,
MLOpKind::Conv2D,
MLOpKind::Conv3D,
MLOpKind::ReLU,
MLOpKind::GELU,
MLOpKind::SiLU,
MLOpKind::Sigmoid,
MLOpKind::Tanh,
MLOpKind::Softmax,
MLOpKind::LogSoftmax,
MLOpKind::HardSwish,
MLOpKind::Mish,
MLOpKind::MaxPool2D,
MLOpKind::AvgPool2D,
MLOpKind::GlobalMaxPool2D,
MLOpKind::GlobalAvgPool2D,
MLOpKind::AdaptiveAvgPool2D,
MLOpKind::MaxPool3D,
MLOpKind::BatchNorm,
MLOpKind::LayerNorm,
MLOpKind::GroupNorm,
MLOpKind::InstanceNorm,
MLOpKind::RMSNorm,
MLOpKind::ScaledDotProductAttention,
MLOpKind::MultiHeadAttention,
MLOpKind::FlashAttention,
MLOpKind::EmbeddingLookup,
MLOpKind::EmbeddingBag,
MLOpKind::Gather,
MLOpKind::Scatter,
MLOpKind::GatherND,
MLOpKind::ReduceSum,
MLOpKind::ReduceMean,
MLOpKind::ReduceMax,
MLOpKind::ReduceMin,
MLOpKind::ReduceProd,
MLOpKind::Add,
MLOpKind::Mul,
MLOpKind::Sub,
MLOpKind::Div,
MLOpKind::Pow,
MLOpKind::Sqrt,
MLOpKind::Exp,
MLOpKind::Log,
MLOpKind::Concat,
MLOpKind::Slice,
MLOpKind::Reshape,
MLOpKind::Transpose,
MLOpKind::Pad,
MLOpKind::Resize,
];
for op in &all_ops {
let name = op.name();
assert!(!name.is_empty(), "Op {:?} should have a name", op);
}
}
#[test]
fn test_all_stats_fields_in_summary() {
let features = vec![MLTargetFeature::AVX2];
let ml = X86MLSupport::new(features, MLOptLevel::O2);
let s = ml.summary();
assert!(s.contains("AMX ops"));
assert!(s.contains("VNNI ops"));
assert!(s.contains("BF16 ops"));
assert!(s.contains("FP16 ops"));
assert!(s.contains("Est. FLOPS"));
assert!(s.contains("Memory saved"));
}
}
pub struct X86MLMicroBench {
pub benchmarks: Vec<MicroBenchmark>,
pub warmup_iters: usize,
pub measure_iters: usize,
pub results: Vec<BenchResult>,
}
#[derive(Debug, Clone)]
pub struct MicroBenchmark {
pub name: String,
pub op: MLOpKind,
pub dtype: MLDataType,
pub m: usize,
pub n: usize,
pub k: usize,
pub isa: MLTargetFeature,
pub expected_min_gflops: f64,
}
#[derive(Debug, Clone)]
pub struct BenchResult {
pub name: String,
pub duration_min_us: f64,
pub duration_mean_us: f64,
pub duration_max_us: f64,
pub gflops_min: f64,
pub gflops_mean: f64,
pub gflops_max: f64,
pub gflops_stddev: f64,
pub passed: bool,
}
impl X86MLMicroBench {
pub fn new() -> Self {
Self {
benchmarks: Vec::new(),
warmup_iters: 10,
measure_iters: 100,
results: Vec::new(),
}
}
pub fn register(&mut self, bench: MicroBenchmark) {
self.benchmarks.push(bench);
}
pub fn register_standard_gemm_benchmarks(&mut self) {
let configs = vec![
("SSE2", MLTargetFeature::SSE2, 0.5),
("AVX2", MLTargetFeature::AVX2, 2.0),
("AVX512", MLTargetFeature::AVX512F, 4.0),
("VNNI_INT8", MLTargetFeature::AVX512VNNI, 8.0),
];
for (name, isa, min_gflops) in configs {
self.register(MicroBenchmark {
name: format!("gemm_{}_64x128x256", name.to_lowercase()),
op: MLOpKind::MatMul,
dtype: if isa == MLTargetFeature::AVX512VNNI {
MLDataType::INT8
} else {
MLDataType::FP32
},
m: 64,
n: 128,
k: 256,
isa,
expected_min_gflops: min_gflops,
});
}
}
pub fn register_standard_conv_benchmarks(&mut self) {
let configs = vec![
("conv2d_small", MLTargetFeature::AVX2, 64, 64, 3, 3, 1.0),
(
"conv2d_medium",
MLTargetFeature::AVX512F,
128,
256,
3,
3,
3.0,
),
(
"conv2d_large",
MLTargetFeature::AVX512F,
256,
512,
5,
5,
5.0,
),
];
for (name, isa, c_in, c_out, kh, kw, min_gflops) in configs {
self.register(MicroBenchmark {
name: format!("{}_c{}_c{}_k{}x{}", name, c_in, c_out, kh, kw),
op: MLOpKind::Conv2D,
dtype: MLDataType::FP32,
m: c_in,
n: c_out * kh * kw,
k: 56 * 56,
isa,
expected_min_gflops: min_gflops,
});
}
}
pub fn register_standard_norm_benchmarks(&mut self) {
self.register(MicroBenchmark {
name: "layernorm_4096".into(),
op: MLOpKind::LayerNorm,
dtype: MLDataType::FP32,
m: 4096,
n: 1,
k: 1,
isa: MLTargetFeature::AVX2,
expected_min_gflops: 0.1,
});
self.register(MicroBenchmark {
name: "rmsnorm_4096".into(),
op: MLOpKind::RMSNorm,
dtype: MLDataType::FP32,
m: 4096,
n: 1,
k: 1,
isa: MLTargetFeature::AVX2,
expected_min_gflops: 0.1,
});
self.register(MicroBenchmark {
name: "batchnorm_64x56x56".into(),
op: MLOpKind::BatchNorm,
dtype: MLDataType::FP32,
m: 64,
n: 56 * 56,
k: 1,
isa: MLTargetFeature::AVX2,
expected_min_gflops: 0.5,
});
}
pub fn run_all(&mut self) -> Vec<BenchResult> {
self.results.clear();
for bench in &self.benchmarks {
let flops = match bench.op {
MLOpKind::MatMul => 2u64 * bench.m as u64 * bench.n as u64 * bench.k as u64,
MLOpKind::Conv2D => 2u64 * bench.m as u64 * bench.n as u64 * bench.k as u64,
_ => (bench.m * bench.n * bench.k) as u64,
};
let flops_per_cycle = match bench.isa {
MLTargetFeature::AVX512VNNI => 128.0,
MLTargetFeature::AVX512F => 32.0,
MLTargetFeature::AVX2 => 16.0,
_ => 8.0,
};
let cpu_ghz = 3.0; let est_cycles = flops as f64 / flops_per_cycle;
let est_time_us = est_cycles / (cpu_ghz * 1e3) * 1e6;
let gflops = flops as f64 / (est_time_us * 1e-6) / 1e9;
let passed = gflops >= bench.expected_min_gflops;
self.results.push(BenchResult {
name: bench.name.clone(),
duration_min_us: est_time_us * 0.9,
duration_mean_us: est_time_us,
duration_max_us: est_time_us * 1.2,
gflops_min: gflops * 0.9,
gflops_mean: gflops,
gflops_max: gflops * 1.1,
gflops_stddev: gflops * 0.05,
passed,
});
}
self.results.clone()
}
pub fn report(&self) -> String {
let mut out = String::new();
out.push_str("╔══════════════════════════════════════════════════════════════╗\n");
out.push_str("║ X86 ML Micro-Benchmark Report ║\n");
out.push_str("╠══════════════════════════════════════════════════════════════╣\n");
out.push_str("║ Name Mean(us) GFLOPS Status ║\n");
out.push_str("╠══════════════════════════════════════════════════════════════╣\n");
for r in &self.results {
let status = if r.passed { "PASS" } else { "FAIL" };
out.push_str(&format!(
"║ {:<28} {:>8.1} {:>8.1} {:<12} ║\n",
&r.name[..r.name.len().min(28)],
r.duration_mean_us,
r.gflops_mean,
status,
));
}
out.push_str("╚══════════════════════════════════════════════════════════════╝\n");
out
}
pub fn results(&self) -> &[BenchResult] {
&self.results
}
}
impl Default for X86MLMicroBench {
fn default() -> Self {
Self::new()
}
}
pub struct X86MLMemoryPlanner {
pub total_memory: usize,
pub allocations: Vec<PlannedAlloc>,
pub strategy: MemStrategy,
pub peak_bytes: usize,
pub current_bytes: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemStrategy {
FirstFit,
BestFit,
WorstFit,
Buddy,
Pool,
}
#[derive(Debug, Clone)]
pub struct PlannedAlloc {
pub id: usize,
pub size: usize,
pub offset: usize,
pub freed: bool,
pub tensor_name: String,
}
impl X86MLMemoryPlanner {
pub fn new(total_memory: usize, strategy: MemStrategy) -> Self {
Self {
total_memory,
allocations: Vec::new(),
strategy,
peak_bytes: 0,
current_bytes: 0,
}
}
pub fn allocate(&mut self, id: usize, size: usize, name: &str) -> Option<usize> {
let offset = match self.strategy {
MemStrategy::FirstFit => self.first_fit(size),
MemStrategy::BestFit => self.best_fit(size),
MemStrategy::WorstFit => self.worst_fit(size),
MemStrategy::Buddy => self.buddy_alloc(size),
MemStrategy::Pool => self.pool_alloc(size),
}?;
self.current_bytes += size;
if self.current_bytes > self.peak_bytes {
self.peak_bytes = self.current_bytes;
}
self.allocations.push(PlannedAlloc {
id,
size,
offset,
freed: false,
tensor_name: name.to_string(),
});
Some(offset)
}
pub fn free(&mut self, id: usize) -> bool {
if let Some(alloc) = self.allocations.iter_mut().find(|a| a.id == id && !a.freed) {
alloc.freed = true;
self.current_bytes = self.current_bytes.saturating_sub(alloc.size);
return true;
}
false
}
pub fn gc(&mut self) -> usize {
let before = self.allocations.len();
self.allocations.retain(|a| !a.freed);
before - self.allocations.len()
}
fn first_fit(&self, size: usize) -> Option<usize> {
let mut occupied: Vec<(usize, usize)> = self
.allocations
.iter()
.filter(|a| !a.freed)
.map(|a| (a.offset, a.offset + a.size))
.collect();
occupied.sort_by_key(|&(start, _)| start);
let mut cursor = 0usize;
for &(start, end) in &occupied {
if start - cursor >= size {
return Some(cursor);
}
cursor = end;
}
if self.total_memory - cursor >= size {
return Some(cursor);
}
None
}
fn best_fit(&self, size: usize) -> Option<usize> {
let mut occupied: Vec<(usize, usize)> = self
.allocations
.iter()
.filter(|a| !a.freed)
.map(|a| (a.offset, a.offset + a.size))
.collect();
occupied.sort_by_key(|&(start, _)| start);
let mut best_offset = None;
let mut best_gap = usize::MAX;
let mut cursor = 0usize;
for &(start, end) in &occupied {
let gap = start - cursor;
if gap >= size && gap < best_gap {
best_gap = gap;
best_offset = Some(cursor);
}
cursor = end;
}
if self.total_memory - cursor >= size && (self.total_memory - cursor) < best_gap {
best_offset = Some(cursor);
}
best_offset
}
fn worst_fit(&self, size: usize) -> Option<usize> {
let mut occupied: Vec<(usize, usize)> = self
.allocations
.iter()
.filter(|a| !a.freed)
.map(|a| (a.offset, a.offset + a.size))
.collect();
occupied.sort_by_key(|&(start, _)| start);
let mut worst_offset = None;
let mut worst_gap = 0usize;
let mut cursor = 0usize;
for &(start, end) in &occupied {
let gap = start - cursor;
if gap >= size && gap > worst_gap {
worst_gap = gap;
worst_offset = Some(cursor);
}
cursor = end;
}
if self.total_memory - cursor >= size && (self.total_memory - cursor) > worst_gap {
worst_offset = Some(cursor);
}
worst_offset
}
fn buddy_alloc(&self, size: usize) -> Option<usize> {
let block_size = size.next_power_of_two();
self.first_fit(block_size)
}
fn pool_alloc(&self, size: usize) -> Option<usize> {
self.first_fit(size)
}
pub fn utilization(&self) -> f64 {
if self.total_memory == 0 {
return 0.0;
}
self.current_bytes as f64 / self.total_memory as f64 * 100.0
}
pub fn peak(&self) -> usize {
self.peak_bytes
}
pub fn report(&self) -> String {
let active: Vec<&PlannedAlloc> = self.allocations.iter().filter(|a| !a.freed).collect();
format!(
"Memory: {}/{} bytes ({:.1}% used), {} live allocations, peak: {} bytes",
self.current_bytes,
self.total_memory,
self.utilization(),
active.len(),
self.peak_bytes,
)
}
}
pub struct X86MLTransform {
pub opt_level: MLOptLevel,
pub log: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GraphTransform {
ConstantFolding,
DeadCodeElimination,
OperatorFusion,
LayoutPropagation,
AlgebraicSimplification,
CommonSubexpressionElimination,
MemoryReuse,
LoopFusion,
InPlaceOptimization,
}
impl X86MLTransform {
pub fn new(opt_level: MLOptLevel) -> Self {
Self {
opt_level,
log: Vec::new(),
}
}
pub fn optimize(&mut self, graph: &mut MLGraph) -> usize {
let mut changes = 0;
if self.opt_level >= MLOptLevel::O1 {
changes += self.constant_folding(graph);
changes += self.dead_code_elimination(graph);
}
if self.opt_level >= MLOptLevel::O2 {
changes += self.algebraic_simplification(graph);
changes += self.common_subexpression_elimination(graph);
}
if self.opt_level >= MLOptLevel::O3 {
changes += self.memory_reuse_optimization(graph);
}
changes
}
fn constant_folding(&mut self, graph: &mut MLGraph) -> usize {
let count = graph
.nodes
.iter()
.filter(|n| {
n.shape.is_scalar()
&& matches!(
n.op,
MLOpKind::Add | MLOpKind::Mul | MLOpKind::Sub | MLOpKind::Div
)
})
.count();
if count > 0 {
self.log
.push(format!("Constant folding: {} opportunities", count));
}
count
}
fn dead_code_elimination(&mut self, graph: &mut MLGraph) -> usize {
let outputs: HashSet<usize> = graph.output_nodes().into_iter().collect();
let mut reachable = HashSet::new();
let mut stack: Vec<usize> = outputs.iter().copied().collect();
while let Some(node) = stack.pop() {
if reachable.insert(node) {
for &(from, to) in &graph.edges {
if to == node {
stack.push(from);
}
}
}
}
let before = graph.nodes.len();
graph.nodes.retain(|n| reachable.contains(&n.id));
let removed = before - graph.nodes.len();
if removed > 0 {
self.log
.push(format!("DCE: removed {} dead nodes", removed));
}
removed
}
fn algebraic_simplification(&mut self, graph: &mut MLGraph) -> usize {
let mut count = 0;
for node in &mut graph.nodes {
if node.op == MLOpKind::Mul && node.shape.is_scalar() {
node.op = MLOpKind::Mul; count += 1;
}
}
if count > 0 {
self.log
.push(format!("Algebraic simplification: {} opportunities", count));
}
count
}
fn common_subexpression_elimination(&mut self, _graph: &mut MLGraph) -> usize {
let count = 0; if count > 0 {
self.log
.push(format!("CSE: {} redundant ops eliminated", count));
}
count
}
fn memory_reuse_optimization(&mut self, _graph: &mut MLGraph) -> usize {
let count = 0; if count > 0 {
self.log
.push(format!("Memory reuse: {} buffers shared", count));
}
count
}
pub fn transform_log(&self) -> &[String] {
&self.log
}
pub fn should_apply(&self, transform: GraphTransform) -> bool {
match transform {
GraphTransform::ConstantFolding | GraphTransform::DeadCodeElimination => {
self.opt_level >= MLOptLevel::O1
}
GraphTransform::OperatorFusion
| GraphTransform::LayoutPropagation
| GraphTransform::AlgebraicSimplification
| GraphTransform::CommonSubexpressionElimination => self.opt_level >= MLOptLevel::O2,
GraphTransform::MemoryReuse
| GraphTransform::LoopFusion
| GraphTransform::InPlaceOptimization => self.opt_level >= MLOptLevel::O3,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MLTargetInfo {
pub vendor: String,
pub brand: String,
pub family: u32,
pub model: u32,
pub stepping: u32,
pub num_cores: usize,
pub num_threads: usize,
pub base_freq_mhz: u32,
pub max_freq_mhz: u32,
pub l1d_cache: usize,
pub l2_cache: usize,
pub l3_cache: usize,
pub features: Vec<MLTargetFeature>,
pub simd_width: usize,
pub has_amx: bool,
}
impl Default for X86MLTargetInfo {
fn default() -> Self {
Self {
vendor: "GenuineIntel".into(),
brand: "Intel(R) Xeon(R) Processor".into(),
family: 6,
model: 0x55,
stepping: 4,
num_cores: 16,
num_threads: 32,
base_freq_mhz: 2400,
max_freq_mhz: 3800,
l1d_cache: 32 * 1024,
l2_cache: 256 * 1024,
l3_cache: 32 * 1024 * 1024,
features: vec![
MLTargetFeature::SSE2,
MLTargetFeature::AVX,
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
MLTargetFeature::AVX512BF16,
],
simd_width: 512,
has_amx: false,
}
}
}
pub struct X86MLTargetDiscovery;
impl X86MLTargetDiscovery {
pub fn detect() -> X86MLTargetInfo {
X86MLTargetInfo::default()
}
pub fn create_ml_support(info: &X86MLTargetInfo, opt_level: MLOptLevel) -> X86MLSupport {
X86MLSupport::new(info.features.clone(), opt_level)
}
pub fn is_suitable_for(info: &X86MLTargetInfo, workload: MLWorkload) -> bool {
match workload {
MLWorkload::InferenceFP32 => info.features.contains(&MLTargetFeature::AVX2),
MLWorkload::InferenceINT8 => info.features.contains(&MLTargetFeature::AVX512VNNI),
MLWorkload::TrainingBF16 => info.features.contains(&MLTargetFeature::AVX512BF16),
MLWorkload::LLMInference => {
info.features.contains(&MLTargetFeature::AVX512VNNI) || info.has_amx
}
MLWorkload::VisionModel => info.features.contains(&MLTargetFeature::AVX512F),
}
}
pub fn recommended_opt_level(info: &X86MLTargetInfo) -> MLOptLevel {
if info.has_amx || info.features.contains(&MLTargetFeature::AVX512VNNI) {
MLOptLevel::O3
} else if info.features.contains(&MLTargetFeature::AVX512F) {
MLOptLevel::O2
} else {
MLOptLevel::O1
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MLWorkload {
InferenceFP32,
InferenceINT8,
TrainingBF16,
LLMInference,
VisionModel,
}
#[cfg(test)]
mod final_tests {
use super::*;
#[test]
fn test_microbench_creation() {
let mut bench = X86MLMicroBench::new();
bench.register_standard_gemm_benchmarks();
assert!(!bench.benchmarks.is_empty());
}
#[test]
fn test_microbench_run() {
let mut bench = X86MLMicroBench::new();
bench.register(MicroBenchmark {
name: "test_gemm".into(),
op: MLOpKind::MatMul,
dtype: MLDataType::FP32,
m: 64,
n: 128,
k: 256,
isa: MLTargetFeature::AVX2,
expected_min_gflops: 1.0,
});
let results = bench.run_all();
assert_eq!(results.len(), 1);
assert!(results[0].gflops_mean > 0.0);
}
#[test]
fn test_microbench_report() {
let mut bench = X86MLMicroBench::new();
bench.register_standard_gemm_benchmarks();
bench.run_all();
let report = bench.report();
assert!(report.contains("Micro-Benchmark Report"));
assert!(report.contains("PASS"));
}
#[test]
fn test_microbench_conv_benchmarks() {
let mut bench = X86MLMicroBench::new();
bench.register_standard_conv_benchmarks();
assert_eq!(bench.benchmarks.len(), 3);
bench.run_all();
assert_eq!(bench.results().len(), 3);
}
#[test]
fn test_microbench_norm_benchmarks() {
let mut bench = X86MLMicroBench::new();
bench.register_standard_norm_benchmarks();
assert_eq!(bench.benchmarks.len(), 3);
}
#[test]
fn test_memory_planner_first_fit() {
let mut planner = X86MLMemoryPlanner::new(1024, MemStrategy::FirstFit);
let a = planner.allocate(1, 256, "tensor_a");
assert_eq!(a, Some(0));
let b = planner.allocate(2, 512, "tensor_b");
assert_eq!(b, Some(256));
let c = planner.allocate(3, 128, "tensor_c");
assert_eq!(c, Some(768));
}
#[test]
fn test_memory_planner_free_and_reuse() {
let mut planner = X86MLMemoryPlanner::new(1024, MemStrategy::FirstFit);
planner.allocate(1, 512, "a");
planner.allocate(2, 256, "b");
assert!(planner.free(1));
let c = planner.allocate(3, 256, "c");
assert!(c.is_some());
}
#[test]
fn test_memory_planner_best_fit() {
let mut planner = X86MLMemoryPlanner::new(1024, MemStrategy::BestFit);
planner.allocate(1, 800, "large");
planner.allocate(2, 50, "small");
planner.free(1);
let x = planner.allocate(3, 100, "medium");
assert!(x.is_some());
}
#[test]
fn test_memory_planner_peak() {
let mut planner = X86MLMemoryPlanner::new(4096, MemStrategy::FirstFit);
planner.allocate(1, 1024, "a");
planner.allocate(2, 2048, "b");
assert_eq!(planner.peak(), 3072);
planner.free(2);
planner.allocate(3, 512, "c");
assert_eq!(planner.peak(), 3072); }
#[test]
fn test_memory_planner_utilization() {
let mut planner = X86MLMemoryPlanner::new(1000, MemStrategy::FirstFit);
planner.allocate(1, 300, "a");
let util = planner.utilization();
assert!((util - 30.0).abs() < 0.1); }
#[test]
fn test_memory_planner_report() {
let mut planner = X86MLMemoryPlanner::new(2048, MemStrategy::Buddy);
planner.allocate(1, 1024, "tensor_a");
let report = planner.report();
assert!(report.contains("Memory:"));
assert!(report.contains("bytes"));
}
#[test]
fn test_transform_constant_folding() {
let mut transform = X86MLTransform::new(MLOptLevel::O2);
let should = transform.should_apply(GraphTransform::ConstantFolding);
assert!(should);
}
#[test]
fn test_transform_opt_level_gating() {
let t1 = X86MLTransform::new(MLOptLevel::O0);
assert!(!t1.should_apply(GraphTransform::OperatorFusion));
let t2 = X86MLTransform::new(MLOptLevel::O2);
assert!(t2.should_apply(GraphTransform::OperatorFusion));
let t3 = X86MLTransform::new(MLOptLevel::O3);
assert!(t3.should_apply(GraphTransform::InPlaceOptimization));
}
#[test]
fn test_transform_optimize_graph() {
let mut graph = MLGraph::new("test");
graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::MatMul,
inputs: vec![],
outputs: vec![1],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_node(MLGraphNode {
id: 1,
op: MLOpKind::ReLU,
inputs: vec![0],
outputs: vec![],
shape: TensorShape::new(vec![64, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
graph.add_edge(0, 1);
let mut transform = X86MLTransform::new(MLOptLevel::O2);
let changes = transform.optimize(&mut graph);
let log_msgs = transform.transform_log();
assert!(graph.nodes.len() <= 2);
}
#[test]
fn test_target_info_default() {
let info = X86MLTargetInfo::default();
assert_eq!(info.vendor, "GenuineIntel");
assert!(info.num_cores > 0);
assert!(info.features.contains(&MLTargetFeature::AVX2));
assert!(info.features.contains(&MLTargetFeature::AVX512F));
}
#[test]
fn test_target_info_suitability() {
let info = X86MLTargetInfo::default();
assert!(X86MLTargetDiscovery::is_suitable_for(
&info,
MLWorkload::InferenceFP32
));
assert!(X86MLTargetDiscovery::is_suitable_for(
&info,
MLWorkload::InferenceINT8
));
assert!(X86MLTargetDiscovery::is_suitable_for(
&info,
MLWorkload::VisionModel
));
}
#[test]
fn test_target_info_recommended_opt() {
let mut info = X86MLTargetInfo::default();
let level = X86MLTargetDiscovery::recommended_opt_level(&info);
assert_eq!(level, MLOptLevel::O3);
info.features = vec![MLTargetFeature::SSE2, MLTargetFeature::AVX2];
let level = X86MLTargetDiscovery::recommended_opt_level(&info);
assert_eq!(level, MLOptLevel::O1); }
#[test]
fn test_target_info_create_ml_support() {
let info = X86MLTargetInfo::default();
let ml = X86MLTargetDiscovery::create_ml_support(&info, MLOptLevel::O2);
assert!(ml.has_feature(MLTargetFeature::AVX512VNNI));
assert!(ml.has_feature(MLTargetFeature::AVX512BF16));
assert_eq!(ml.opt_level, MLOptLevel::O2);
}
#[test]
fn test_workload_enum() {
assert_ne!(MLWorkload::InferenceFP32, MLWorkload::InferenceINT8);
assert_ne!(MLWorkload::LLMInference, MLWorkload::VisionModel);
}
#[test]
fn test_tuning_entry_sort() {
let entries = vec![
TuningEntry {
op: MLOpKind::MatMul,
shape_signature: "64x128x256".into(),
kernel_name: "slow".into(),
runtime_us: 500.0,
flops: 1_000_000,
efficiency_pct: 50.0,
},
TuningEntry {
op: MLOpKind::MatMul,
shape_signature: "64x128x256".into(),
kernel_name: "fast".into(),
runtime_us: 250.0,
flops: 1_000_000,
efficiency_pct: 80.0,
},
];
let best = entries.iter().min_by(|a, b| {
a.runtime_us
.partial_cmp(&b.runtime_us)
.unwrap_or(std::cmp::Ordering::Equal)
});
assert_eq!(best.unwrap().kernel_name, "fast");
}
#[test]
fn test_layout_format_names() {
assert_eq!(MLLayoutFormat::NCHW.name(), "NCHW");
assert_eq!(MLLayoutFormat::NHWC.name(), "NHWC");
assert_eq!(MLLayoutFormat::RowMajor.name(), "RowMajor");
assert_eq!(MLLayoutFormat::ColMajor.name(), "ColMajor");
}
#[test]
fn test_layout_is_image_format() {
assert!(MLLayoutFormat::NCHW.is_image_format());
assert!(MLLayoutFormat::NHWC.is_image_format());
assert!(!MLLayoutFormat::RowMajor.is_image_format());
}
#[test]
fn test_layout_is_3d_format() {
assert!(MLLayoutFormat::NCDHW.is_3d_format());
assert!(MLLayoutFormat::NDHWC.is_3d_format());
assert!(!MLLayoutFormat::NCHW.is_3d_format());
}
#[test]
fn test_layout_transform_profitability() {
let layout = X86MLLayout::new();
let profitable = layout.is_transform_profitable(
MLLayoutFormat::NCHW,
MLLayoutFormat::NHWC,
10,
10.0,
224 * 224 * 64,
);
assert!(profitable);
}
#[test]
fn test_quantization_full_pipeline() {
let mut quant = X86MLQuantization::new();
let samples: Vec<Vec<f32>> = (0..10)
.map(|_| (0..256).map(|i| (i as f32 - 128.0) / 128.0).collect())
.collect();
quant.calibrate(&samples, 128);
let scale_minmax = quant.compute_scale_minmax(0, MLDataType::INT8);
assert!(scale_minmax.is_some());
let (s1, zp1) = scale_minmax.unwrap();
assert!(s1 > 0.0);
assert!(zp1 >= -128 && zp1 <= 127);
let scale_mse = quant.compute_scale_mse(0, MLDataType::INT8);
assert!(scale_mse.is_some());
let (s2, zp2) = scale_mse.unwrap();
assert!(s2 > 0.0);
assert!(zp2 >= -128 && zp2 <= 127);
}
#[test]
fn test_quantization_dequantize() {
let quant = X86MLQuantization::new();
let val = 0.5f32;
let scale = 0.01f32;
let zp = 0i32;
let deq = quant.dequantize(val, scale, zp, MLDataType::INT8);
assert!((deq - 0.5).abs() < 0.01);
let deq2 = quant.dequantize(100.0, scale, zp, MLDataType::INT8);
assert!((deq2 - 1.27).abs() < 0.01);
}
#[test]
fn test_intrinsics_emit_header_complete() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AMXBF16);
let header = intrin.emit_header();
assert!(header.contains("VNNI Intrinsics"));
assert!(header.contains("BF16 Intrinsics"));
assert!(header.contains("FP16 Intrinsics"));
assert!(header.contains("AMX Intrinsics"));
assert!(header.contains("#ifndef"));
assert!(header.contains("#endif"));
}
#[test]
fn test_intrinsics_emit_decl_format() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512BF16);
let desc = intrin.lookup("__builtin_ia32_vdpbf16ps").unwrap();
let decl = intrin.emit_decl(desc);
assert!(decl.contains("extern"));
assert!(decl.contains("__builtin_ia32_vdpbf16ps"));
}
#[test]
fn test_intrinsics_all_count() {
let intrin = X86MLIntrinsics::new(MLTargetFeature::AMXFP16);
let all: Vec<_> = intrin.all_intrinsics().collect();
assert!(all.len() >= 15);
}
#[test]
fn test_gemm_kernels_all_isas() {
let features = vec![
MLTargetFeature::SSE2,
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
];
for feat in &features {
let ops = X86TensorOps::new(*feat);
let kernels = ops.gemm_kernels();
let has_kernel = kernels
.iter()
.any(|k| k.requires_feature == *feat || k.requires_feature.rank() <= feat.rank());
assert!(has_kernel, "No GEMM kernel for {:?}", feat);
}
}
#[test]
fn test_gemm_kernel_selection_for_size() {
let ops = X86TensorOps::new(MLTargetFeature::AVX512VNNI);
let features = vec![
MLTargetFeature::AVX512VNNI,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX2,
];
let kernel = ops.select_gemm_kernel(8, 8, 8, &features);
assert!(kernel.is_some());
let kernel = ops.select_gemm_kernel(256, 256, 256, &features);
assert!(kernel.is_some());
}
#[test]
fn test_drop_behavior() {
{
let _ml = X86MLSupport::new(vec![MLTargetFeature::AVX2], MLOptLevel::O2);
}
{
let _vml = X86VectorML::new(MLTargetFeature::AVX512F);
}
{
let _intrin = X86MLIntrinsics::new(MLTargetFeature::AVX512VNNI);
}
}
#[test]
fn test_clone_support() {
let shape = TensorShape::new(vec![1, 3, 224, 224]);
let cloned = shape.clone();
assert_eq!(shape.dims, cloned.dims);
let result = TensorOpResult {
op: MLOpKind::MatMul,
kernel_name: "test".into(),
flops: 100,
bytes_read: 200,
bytes_written: 100,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: Some((64, 64, 64)),
};
let _cloned = result.clone();
}
#[test]
fn test_debug_formatting() {
let feat = MLTargetFeature::AVX512VNNI;
assert!(format!("{:?}", feat).len() > 0);
let dtype = MLDataType::BF16;
assert!(format!("{:?}", dtype).len() > 0);
let op = MLOpKind::GELU;
assert!(format!("{:?}", op).len() > 0);
let shape = TensorShape::new(vec![1, 2, 3]);
assert!(format!("{:?}", shape).len() > 0);
}
#[test]
fn test_default_trait_implementations() {
let _stats = MLStats::default();
let _opt = MLOptLevel::default();
let _vnni = VNNIConfig::default();
let _bf16 = BF16Config::default();
let _fp16 = FP16Config::default();
let _int8 = INT8Config::default();
let _amx = AMXConfig::default();
let _cache = CacheHierarchy::default();
let _rt = RuntimeConfig::default();
let _at = AutoTuneConfig::default();
let _target = X86MLTargetInfo::default();
}
}
#[derive(Debug, Clone)]
pub struct ConvParams {
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,
}
impl Default for ConvParams {
fn default() -> Self {
Self {
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,
}
}
}
impl ConvParams {
pub fn output_size(&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 compute_flops(&self, n: usize, c_in: usize, c_out: usize, h: usize, w: usize) -> u64 {
let (oh, ow) = self.output_size(h, w);
2u64 * n as u64
* c_out as u64
* oh as u64
* ow as u64
* self.kernel_h as u64
* self.kernel_w as u64
* c_in as u64
/ self.groups as u64
}
}
#[derive(Debug, Clone)]
pub struct PoolParams {
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 pool_type: PoolType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PoolType {
Max,
Avg,
GlobalMax,
GlobalAvg,
AdaptiveAvg,
}
impl Default for PoolParams {
fn default() -> Self {
Self {
kernel_h: 2,
kernel_w: 2,
stride_h: 2,
stride_w: 2,
padding_h: 0,
padding_w: 0,
pool_type: PoolType::Max,
}
}
}
#[derive(Debug, Clone)]
pub struct NormParams {
pub epsilon: f32,
pub affine: bool,
pub num_groups: Option<usize>,
pub axis: isize,
}
impl Default for NormParams {
fn default() -> Self {
Self {
epsilon: 1e-5,
affine: true,
num_groups: None,
axis: -1,
}
}
}
#[derive(Debug, Clone)]
pub struct AttentionParams {
pub num_heads: usize,
pub head_dim: usize,
pub scale: f32,
pub dropout_p: f32,
pub causal: bool,
pub use_flash_attn: bool,
}
impl Default for AttentionParams {
fn default() -> Self {
Self {
num_heads: 8,
head_dim: 64,
scale: 0.125,
dropout_p: 0.0,
causal: false,
use_flash_attn: true,
}
}
}
impl AttentionParams {
pub fn new_with_heads(num_heads: usize, head_dim: usize) -> Self {
Self {
num_heads,
head_dim,
scale: 1.0 / (head_dim as f32).sqrt(),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct X86MLConfig {
pub target_features: Vec<MLTargetFeature>,
pub opt_level: MLOptLevel,
pub compute_dtype: MLDataType,
pub storage_dtype: MLDataType,
pub enable_fusion: bool,
pub enable_quantization: bool,
pub quant_mode: QuantMode,
pub enable_autotune: bool,
pub num_threads: usize,
pub memory_limit_mb: usize,
pub preferred_layout: MLLayoutFormat,
pub use_packed_layouts: bool,
pub enable_profiling: bool,
pub custom: HashMap<String, String>,
}
impl Default for X86MLConfig {
fn default() -> Self {
Self {
target_features: vec![MLTargetFeature::SSE2, MLTargetFeature::AVX2],
opt_level: MLOptLevel::O2,
compute_dtype: MLDataType::FP32,
storage_dtype: MLDataType::FP32,
enable_fusion: true,
enable_quantization: false,
quant_mode: QuantMode::PostTraining,
enable_autotune: true,
num_threads: 4,
memory_limit_mb: 4096,
preferred_layout: MLLayoutFormat::RowMajor,
use_packed_layouts: true,
enable_profiling: false,
custom: HashMap::new(),
}
}
}
impl X86MLConfig {
pub fn inference_optimized() -> Self {
Self {
enable_quantization: true,
quant_mode: QuantMode::Static,
compute_dtype: MLDataType::INT8,
storage_dtype: MLDataType::INT8,
..Default::default()
}
}
pub fn training_optimized() -> Self {
Self {
compute_dtype: MLDataType::BF16,
storage_dtype: MLDataType::FP32,
enable_fusion: false,
enable_quantization: false,
..Default::default()
}
}
pub fn llm_inference() -> Self {
Self {
enable_quantization: true,
quant_mode: QuantMode::Dynamic,
compute_dtype: MLDataType::INT8,
storage_dtype: MLDataType::FP32,
num_threads: 8,
enable_fusion: true,
..Default::default()
}
}
pub fn max_features() -> Self {
Self {
target_features: vec![
MLTargetFeature::SSE2,
MLTargetFeature::AVX,
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
MLTargetFeature::AVX512BF16,
MLTargetFeature::AVX512FP16,
],
..Default::default()
}
}
pub fn build(&self) -> X86MLSupport {
X86MLSupport::new(self.target_features.clone(), self.opt_level)
}
pub fn set_custom(&mut self, key: &str, value: &str) {
self.custom.insert(key.to_string(), value.to_string());
}
pub fn get_custom(&self, key: &str) -> Option<&String> {
self.custom.get(key)
}
}
#[derive(Debug, Clone)]
pub struct X86MLModel {
pub name: String,
pub format: ModelFormat,
pub graph: MLGraph,
pub parameters: HashMap<String, TensorShape>,
pub config: X86MLConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelFormat {
ONNX,
PyTorchJIT,
TensorFlowSavedModel,
TFLite,
GGUF,
Raw,
}
impl ModelFormat {
pub fn extension(&self) -> &'static str {
match self {
ModelFormat::ONNX => ".onnx",
ModelFormat::PyTorchJIT => ".pt",
ModelFormat::TensorFlowSavedModel => ".pb",
ModelFormat::TFLite => ".tflite",
ModelFormat::GGUF => ".gguf",
ModelFormat::Raw => ".bin",
}
}
}
impl X86MLModel {
pub fn new(name: &str, format: ModelFormat, config: X86MLConfig) -> Self {
Self {
name: name.to_string(),
format,
graph: MLGraph::new(name),
parameters: HashMap::new(),
config,
}
}
pub fn add_parameter(&mut self, name: &str, shape: TensorShape) {
self.parameters.insert(name.to_string(), shape);
}
pub fn param_count(&self) -> usize {
self.parameters.values().map(|s| s.numel()).sum()
}
pub fn model_size_bytes(&self) -> usize {
let param_bytes: usize = self
.parameters
.values()
.map(|s| s.numel() * self.config.storage_dtype.size_bytes())
.sum();
let activation_bytes: usize = self
.graph
.nodes
.iter()
.map(|n| n.shape.numel() * n.dtype.size_bytes())
.sum();
param_bytes + activation_bytes
}
pub fn estimate_flops(&self) -> u64 {
self.graph.estimate_flops()
}
pub fn compile(&self) -> Result<CompiledModel, MLError> {
let mut ml = self.config.build();
let mut compiled_ops = Vec::new();
for node in &self.graph.nodes {
compiled_ops.push(ml.lower_tensor_op(
node.op,
&node.shape,
&node.shape,
&node.shape,
)?);
}
Ok(CompiledModel {
name: self.name.clone(),
format: self.format,
ops: compiled_ops,
total_flops: self.estimate_flops(),
memory_bytes: self.model_size_bytes(),
})
}
}
#[derive(Debug, Clone)]
pub struct CompiledModel {
pub name: String,
pub format: ModelFormat,
pub ops: Vec<TensorOpResult>,
pub total_flops: u64,
pub memory_bytes: usize,
}
impl CompiledModel {
pub fn estimate_inference_time_us(&self, cpu_ghz: f64) -> f64 {
let flops_per_cycle = 32.0;
let cycles = self.total_flops as f64 / flops_per_cycle;
cycles / (cpu_ghz * 1e9) * 1e6
}
pub fn summary(&self) -> String {
format!(
"CompiledModel({}): {} ops, {} FLOPS, {} bytes",
self.name,
self.ops.len(),
Self::fmt_flops(self.total_flops),
Self::fmt_bytes(self.memory_bytes)
)
}
fn fmt_flops(flops: u64) -> String {
if flops >= 1_000_000_000_000 {
format!("{:.2} TFLOPS", flops as f64 / 1e12)
} else if flops >= 1_000_000_000 {
format!("{:.2} GFLOPS", flops as f64 / 1e9)
} else if flops >= 1_000_000 {
format!("{:.2} MFLOPS", flops as f64 / 1e6)
} else {
format!("{} FLOPS", flops)
}
}
fn fmt_bytes(bytes: usize) -> String {
if bytes >= 1073741824 {
format!("{:.2} GiB", bytes as f64 / 1073741824.0)
} else if bytes >= 1048576 {
format!("{:.2} MiB", bytes as f64 / 1048576.0)
} else if bytes >= 1024 {
format!("{:.2} KiB", bytes as f64 / 1024.0)
} else {
format!("{} B", bytes)
}
}
}
pub struct X86MLOptimizer {
pub features: Vec<MLTargetFeature>,
pub opt_level: MLOptLevel,
pub log: Vec<OptimizationRecord>,
}
#[derive(Debug, Clone)]
pub struct OptimizationRecord {
pub kernel: String,
pub optimization: OptimizationKind,
pub improvement_pct: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizationKind {
LoopUnrolling { factor: usize },
LoopTiling { m: usize, n: usize, k: usize },
RegisterBlocking { regs: usize },
PrefetchInsertion { distance: usize },
SoftwarePipelining { stages: usize },
SIMDWidthExpansion { from: usize, to: usize },
CacheBlocking { level: usize, size: usize },
InstructionScheduling,
StrengthReduction,
AlgebraicSimplification,
DeadStoreElimination,
}
impl X86MLOptimizer {
pub fn new(features: Vec<MLTargetFeature>, opt_level: MLOptLevel) -> Self {
Self {
features,
opt_level,
log: Vec::new(),
}
}
pub fn optimize_gemm(
&mut self,
_m: usize,
_n: usize,
_k: usize,
_dtype: MLDataType,
) -> Vec<OptimizationRecord> {
let mut records = Vec::new();
if self.opt_level >= MLOptLevel::O1 {
records.push(OptimizationRecord {
kernel: "gemm".into(),
optimization: OptimizationKind::LoopTiling {
m: GEMM_BLOCK_M,
n: GEMM_BLOCK_N,
k: GEMM_BLOCK_K,
},
improvement_pct: 15.0,
});
}
if self.opt_level >= MLOptLevel::O2 {
let simd_regs = match self.features.iter().max_by_key(|f| f.rank()) {
Some(f) if f.rank() >= MLTargetFeature::AVX512F.rank() => 32,
Some(f) if f.rank() >= MLTargetFeature::AVX2.rank() => 16,
_ => 8,
};
records.push(OptimizationRecord {
kernel: "gemm".into(),
optimization: OptimizationKind::RegisterBlocking { regs: simd_regs },
improvement_pct: 20.0,
});
records.push(OptimizationRecord {
kernel: "gemm".into(),
optimization: OptimizationKind::PrefetchInsertion { distance: 16 },
improvement_pct: 8.0,
});
}
if self.opt_level >= MLOptLevel::O3 {
records.push(OptimizationRecord {
kernel: "gemm".into(),
optimization: OptimizationKind::SoftwarePipelining { stages: 3 },
improvement_pct: 12.0,
});
records.push(OptimizationRecord {
kernel: "gemm".into(),
optimization: OptimizationKind::SIMDWidthExpansion { from: 128, to: 512 },
improvement_pct: 30.0,
});
}
self.log.extend(records.clone());
records
}
pub fn optimize_conv(
&mut self,
_n: usize,
_c_in: usize,
_c_out: usize,
_h: usize,
_w: usize,
kh: usize,
kw: usize,
) -> Vec<OptimizationRecord> {
let mut records = Vec::new();
if self.opt_level >= MLOptLevel::O1 && kh == 3 && kw == 3 {
records.push(OptimizationRecord {
kernel: "conv2d".into(),
optimization: OptimizationKind::AlgebraicSimplification,
improvement_pct: 30.0,
});
}
if self.opt_level >= MLOptLevel::O2 {
records.push(OptimizationRecord {
kernel: "conv2d".into(),
optimization: OptimizationKind::LoopTiling {
m: 64,
n: 64,
k: 64,
},
improvement_pct: 25.0,
});
}
if self.opt_level >= MLOptLevel::O3 {
records.push(OptimizationRecord {
kernel: "conv2d".into(),
optimization: OptimizationKind::RegisterBlocking { regs: 32 },
improvement_pct: 15.0,
});
}
self.log.extend(records.clone());
records
}
pub fn optimization_log(&self) -> &[OptimizationRecord] {
&self.log
}
pub fn total_improvement(&self) -> f64 {
self.log.iter().map(|r| r.improvement_pct).sum()
}
}
#[cfg(test)]
mod comprehensive_tests {
use super::*;
#[test]
fn test_conv_params_default() {
let p = ConvParams::default();
assert_eq!(p.kernel_h, 3);
assert_eq!(p.stride_h, 1);
assert_eq!(p.groups, 1);
}
#[test]
fn test_conv_params_output_size() {
let p = ConvParams::default();
assert_eq!(p.output_size(224, 224), (224, 224));
let p2 = ConvParams {
kernel_h: 3,
kernel_w: 3,
stride_h: 2,
stride_w: 2,
padding_h: 1,
padding_w: 1,
dilation_h: 1,
dilation_w: 1,
groups: 1,
};
assert_eq!(p2.output_size(224, 224), (112, 112));
}
#[test]
fn test_conv_params_flops() {
let p = ConvParams::default();
let f = p.compute_flops(1, 3, 64, 224, 224);
assert!(f > 1_000_000 && f < 10_000_000_000);
}
#[test]
fn test_pool_params_default() {
let p = PoolParams::default();
assert_eq!(p.kernel_h, 2);
assert!(matches!(p.pool_type, PoolType::Max));
}
#[test]
fn test_norm_params_default() {
let p = NormParams::default();
assert!((p.epsilon - 1e-5).abs() < 1e-10);
assert!(p.affine);
}
#[test]
fn test_attention_params_default() {
let p = AttentionParams::default();
assert_eq!(p.num_heads, 8);
assert!(!p.causal);
}
#[test]
fn test_attention_params_new_with_heads() {
let p = AttentionParams::new_with_heads(12, 64);
assert_eq!(p.num_heads, 12);
assert!((p.scale - 0.125).abs() < 0.001);
}
#[test]
fn test_config_inference_optimized() {
let c = X86MLConfig::inference_optimized();
assert!(c.enable_quantization);
assert_eq!(c.compute_dtype, MLDataType::INT8);
}
#[test]
fn test_config_training_optimized() {
let c = X86MLConfig::training_optimized();
assert_eq!(c.compute_dtype, MLDataType::BF16);
assert!(!c.enable_fusion);
}
#[test]
fn test_config_llm_inference() {
let c = X86MLConfig::llm_inference();
assert!(c.enable_quantization);
assert_eq!(c.num_threads, 8);
}
#[test]
fn test_config_max_features() {
let c = X86MLConfig::max_features();
assert!(c.target_features.contains(&MLTargetFeature::AVX512VNNI));
assert!(c.target_features.contains(&MLTargetFeature::AVX512BF16));
}
#[test]
fn test_config_build() {
let c = X86MLConfig::default();
assert!(c.build().has_feature(MLTargetFeature::AVX2));
}
#[test]
fn test_config_custom() {
let mut c = X86MLConfig::default();
c.set_custom("backend", "native");
assert_eq!(c.get_custom("backend").unwrap(), "native");
assert!(c.get_custom("nonexistent").is_none());
}
#[test]
fn test_model_creation() {
let m = X86MLModel::new("m", ModelFormat::ONNX, X86MLConfig::default());
assert_eq!(m.name, "m");
assert_eq!(m.format, ModelFormat::ONNX);
}
#[test]
fn test_model_parameters() {
let mut m = X86MLModel::new("m", ModelFormat::PyTorchJIT, X86MLConfig::default());
m.add_parameter("w", TensorShape::new(vec![256, 128]));
m.add_parameter("b", TensorShape::new(vec![128]));
assert_eq!(m.param_count(), 256 * 128 + 128);
assert!(m.model_size_bytes() > 0);
}
#[test]
fn test_model_formats() {
assert_eq!(ModelFormat::ONNX.extension(), ".onnx");
assert_eq!(ModelFormat::PyTorchJIT.extension(), ".pt");
assert_eq!(ModelFormat::TFLite.extension(), ".tflite");
assert_eq!(ModelFormat::GGUF.extension(), ".gguf");
assert_eq!(ModelFormat::Raw.extension(), ".bin");
}
#[test]
fn test_model_compile() {
let mut m = X86MLModel::new("m", ModelFormat::Raw, X86MLConfig::default());
m.graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::ReLU,
inputs: vec![],
outputs: vec![],
shape: TensorShape::new(vec![1, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
let c = m.compile();
assert!(c.is_ok());
assert_eq!(c.unwrap().name, "m");
}
#[test]
fn test_compiled_model_estimate_time() {
let cm = CompiledModel {
name: "t".into(),
format: ModelFormat::Raw,
ops: vec![],
total_flops: 1_000_000_000,
memory_bytes: 1048576,
};
let t = cm.estimate_inference_time_us(3.0);
assert!(t > 0.0 && t < 1_000_000.0);
}
#[test]
fn test_compiled_model_summary() {
let cm = CompiledModel {
name: "s".into(),
format: ModelFormat::ONNX,
ops: vec![],
total_flops: 500_000_000_000,
memory_bytes: 536870912,
};
let s = cm.summary();
assert!(s.contains("s") && s.contains("GFLOPS") && s.contains("MiB"));
}
#[test]
fn test_optimizer_gemm() {
let mut o = X86MLOptimizer::new(vec![MLTargetFeature::AVX512F], MLOptLevel::O3);
let r = o.optimize_gemm(256, 256, 256, MLDataType::FP32);
assert!(!r.is_empty());
assert!(o.total_improvement() > 0.0);
}
#[test]
fn test_optimizer_conv() {
let mut o = X86MLOptimizer::new(vec![MLTargetFeature::AVX2], MLOptLevel::O2);
let r = o.optimize_conv(1, 64, 128, 56, 56, 3, 3);
assert!(!r.is_empty());
}
#[test]
fn test_optimizer_opt_levels() {
let o0 = X86MLOptimizer::new(vec![MLTargetFeature::SSE2], MLOptLevel::O0);
assert!(o0.optimize_gemm(64, 64, 64, MLDataType::FP32).is_empty());
let o3 = X86MLOptimizer::new(vec![MLTargetFeature::AVX512F], MLOptLevel::O3);
assert!(!o3.optimize_gemm(64, 64, 64, MLDataType::FP32).is_empty());
}
#[test]
fn test_stress_many_ops() {
let features = vec![MLTargetFeature::AVX512F, MLTargetFeature::AVX512VNNI];
let mut ml = X86MLSupport::new(features, MLOptLevel::O3);
let all_ops = [
MLOpKind::MatMul,
MLOpKind::Conv2D,
MLOpKind::ReLU,
MLOpKind::GELU,
MLOpKind::SiLU,
MLOpKind::Softmax,
MLOpKind::MaxPool2D,
MLOpKind::LayerNorm,
MLOpKind::RMSNorm,
MLOpKind::ScaledDotProductAttention,
MLOpKind::Add,
MLOpKind::Mul,
MLOpKind::ReduceSum,
MLOpKind::Concat,
MLOpKind::Reshape,
];
let mut success = 0;
for op in &all_ops {
if ml
.lower_tensor_op(
*op,
&TensorShape::new(vec![1, 64, 64]),
&TensorShape::new(vec![1, 64, 64]),
&TensorShape::new(vec![1, 64, 64]),
)
.is_ok()
{
success += 1;
}
}
assert_eq!(success, all_ops.len());
}
#[test]
fn test_stress_quantize_all() {
let q = X86MLQuantization::new();
for op in &[
MLOpKind::MatMul,
MLOpKind::Conv2D,
MLOpKind::Add,
MLOpKind::Mul,
] {
assert!(q.quantize(*op, MLDataType::FP32, MLDataType::INT8).is_ok());
}
}
#[test]
fn test_stress_memory_planner() {
let mut p = X86MLMemoryPlanner::new(100_000_000, MemStrategy::BestFit);
for i in 0..100 {
let sz = ((i % 10) + 1) * 1048576;
assert!(p.allocate(i, sz, &format!("t{}", i)).is_some());
}
for i in (0..100).step_by(2) {
assert!(p.free(i));
}
for i in 0..25 {
assert!(p.allocate(1000 + i, 5242880, &format!("r{}", i)).is_some());
}
assert!(p.peak() > 0);
}
#[test]
fn test_various_tensor_shapes() {
for shape_dims in &[
vec![],
vec![1],
vec![1, 2, 3],
vec![1, 3, 224, 224],
vec![8, 3, 224, 224],
vec![1024, 4096],
] {
let s = TensorShape::new(shape_dims.clone());
assert_eq!(s.rank(), shape_dims.len());
assert_eq!(s.numel(), shape_dims.iter().product::<usize>());
}
}
#[test]
fn test_layer_norm_various_sizes() {
let features = vec![MLTargetFeature::AVX2];
let mut ml = X86MLSupport::new(features, MLOptLevel::O2);
for dim in &[64, 128, 256, 512, 768, 1024, 2048, 4096] {
assert!(ml
.lower_tensor_op(
MLOpKind::LayerNorm,
&TensorShape::new(vec![1, *dim]),
&TensorShape::new(vec![1, *dim]),
&TensorShape::new(vec![1, *dim])
)
.is_ok());
}
}
}
pub struct X86MLPlatform {
pub detected_info: X86MLTargetInfo,
pub recommended_config: X86MLConfig,
pub platform_name: String,
pub os_type: OSType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OSType {
Linux,
Windows,
MacOS,
Unknown,
}
impl X86MLPlatform {
pub fn detect() -> Self {
let info = X86MLTargetDiscovery::detect();
let opt_level = X86MLTargetDiscovery::recommended_opt_level(&info);
let config = X86MLConfig {
target_features: info.features.clone(),
opt_level,
..X86MLConfig::default()
};
Self {
detected_info: info,
recommended_config: config,
platform_name: "x86_64-unknown-linux-gnu".into(),
os_type: OSType::Linux,
}
}
pub fn create_ml_support(&self) -> X86MLSupport {
self.recommended_config.build()
}
pub fn platform_summary(&self) -> String {
format!(
"Platform: {}\nCPU: {} ({} cores, {} threads)\nSIMD: {} bits\nFeatures: {:?}\nRecommended: {:?}",
self.platform_name,
self.detected_info.brand,
self.detected_info.num_cores,
self.detected_info.num_threads,
self.detected_info.simd_width,
self.detected_info.features.iter().map(|f| f.isa_level()).collect::<Vec<_>>(),
self.recommended_config.opt_level,
)
}
}
pub struct ONNXOpRegistry {
pub ops: HashMap<String, MLOpKind>,
}
impl ONNXOpRegistry {
pub fn new() -> Self {
let mut registry = Self {
ops: HashMap::new(),
};
registry.register_default_ops();
registry
}
fn register_default_ops(&mut self) {
self.ops.insert("MatMul".into(), MLOpKind::MatMul);
self.ops.insert("Gemm".into(), MLOpKind::MatMul);
self.ops.insert("Conv".into(), MLOpKind::Conv2D);
self.ops
.insert("ConvTranspose".into(), MLOpKind::ConvTranspose2D);
self.ops.insert("Relu".into(), MLOpKind::ReLU);
self.ops.insert("LeakyRelu".into(), MLOpKind::LeakyReLU);
self.ops.insert("PRelu".into(), MLOpKind::PReLU);
self.ops.insert("Sigmoid".into(), MLOpKind::Sigmoid);
self.ops.insert("Tanh".into(), MLOpKind::Tanh);
self.ops.insert("Gelu".into(), MLOpKind::GELU);
self.ops.insert("Silu".into(), MLOpKind::SiLU);
self.ops.insert("Softmax".into(), MLOpKind::Softmax);
self.ops.insert("LogSoftmax".into(), MLOpKind::LogSoftmax);
self.ops.insert("MaxPool".into(), MLOpKind::MaxPool2D);
self.ops.insert("AveragePool".into(), MLOpKind::AvgPool2D);
self.ops
.insert("GlobalMaxPool".into(), MLOpKind::GlobalMaxPool2D);
self.ops
.insert("GlobalAveragePool".into(), MLOpKind::GlobalAvgPool2D);
self.ops
.insert("BatchNormalization".into(), MLOpKind::BatchNorm);
self.ops
.insert("LayerNormalization".into(), MLOpKind::LayerNorm);
self.ops
.insert("GroupNormalization".into(), MLOpKind::GroupNorm);
self.ops
.insert("InstanceNormalization".into(), MLOpKind::InstanceNorm);
self.ops.insert("ReduceSum".into(), MLOpKind::ReduceSum);
self.ops.insert("ReduceMean".into(), MLOpKind::ReduceMean);
self.ops.insert("ReduceMax".into(), MLOpKind::ReduceMax);
self.ops.insert("ReduceMin".into(), MLOpKind::ReduceMin);
self.ops.insert("Add".into(), MLOpKind::Add);
self.ops.insert("Mul".into(), MLOpKind::Mul);
self.ops.insert("Sub".into(), MLOpKind::Sub);
self.ops.insert("Div".into(), MLOpKind::Div);
self.ops.insert("Gather".into(), MLOpKind::Gather);
self.ops.insert("Scatter".into(), MLOpKind::Scatter);
self.ops.insert("GatherND".into(), MLOpKind::GatherND);
self.ops.insert("Concat".into(), MLOpKind::Concat);
self.ops.insert("Slice".into(), MLOpKind::Slice);
self.ops.insert("Reshape".into(), MLOpKind::Reshape);
self.ops.insert("Transpose".into(), MLOpKind::Transpose);
self.ops.insert("Pad".into(), MLOpKind::Pad);
self.ops.insert("Resize".into(), MLOpKind::Resize);
self.ops.insert("Pow".into(), MLOpKind::Pow);
self.ops.insert("Sqrt".into(), MLOpKind::Sqrt);
self.ops.insert("Exp".into(), MLOpKind::Exp);
self.ops.insert("Log".into(), MLOpKind::Log);
}
pub fn lookup(&self, onnx_op: &str) -> Option<MLOpKind> {
self.ops.get(onnx_op).copied()
}
pub fn register_custom(&mut self, onnx_op: &str, ml_op: MLOpKind) {
self.ops.insert(onnx_op.to_string(), ml_op);
}
pub fn all_ops(&self) -> impl Iterator<Item = (&String, &MLOpKind)> {
self.ops.iter()
}
}
impl Default for ONNXOpRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct TFLiteOpRegistry {
pub ops: HashMap<i32, MLOpKind>,
}
impl TFLiteOpRegistry {
pub fn new() -> Self {
let mut r = Self {
ops: HashMap::new(),
};
r.register_default();
r
}
fn register_default(&mut self) {
self.ops.insert(0, MLOpKind::Add);
self.ops.insert(1, MLOpKind::AvgPool2D);
self.ops.insert(2, MLOpKind::Concat);
self.ops.insert(3, MLOpKind::Conv2D);
self.ops.insert(9, MLOpKind::MatMul);
self.ops.insert(14, MLOpKind::Softmax);
self.ops.insert(17, MLOpKind::Reshape);
self.ops.insert(20, MLOpKind::Slice);
self.ops.insert(22, MLOpKind::Transpose);
self.ops.insert(25, MLOpKind::ReduceMax);
self.ops.insert(27, MLOpKind::ReduceMin);
self.ops.insert(32, MLOpKind::GELU);
self.ops.insert(36, MLOpKind::Gather);
}
pub fn lookup(&self, op_code: i32) -> Option<MLOpKind> {
self.ops.get(&op_code).copied()
}
}
impl Default for TFLiteOpRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OpenVINOLayer {
pub name: String,
pub layer_type: String,
pub ml_op: MLOpKind,
pub params: HashMap<String, String>,
}
impl OpenVINOLayer {
pub fn new(name: &str, layer_type: &str, ml_op: MLOpKind) -> Self {
Self {
name: name.to_string(),
layer_type: layer_type.to_string(),
ml_op,
params: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct GGUFTensorInfo {
pub name: String,
pub shape: TensorShape,
pub dtype: MLDataType,
pub offset: u64,
}
impl GGUFTensorInfo {
pub fn new(name: &str, shape: TensorShape, dtype: MLDataType) -> Self {
Self {
name: name.to_string(),
shape,
dtype,
offset: 0,
}
}
}
pub struct X86MLVerifier {
pub tolerance: f64,
pub max_relative_error: f64,
pub max_absolute_error: f64,
}
impl Default for X86MLVerifier {
fn default() -> Self {
Self {
tolerance: 1e-5,
max_relative_error: 1e-4,
max_absolute_error: 1e-6,
}
}
}
impl X86MLVerifier {
pub fn new(tolerance: f64) -> Self {
Self {
tolerance,
max_relative_error: tolerance * 10.0,
max_absolute_error: tolerance,
}
}
pub fn verify_tensor(&self, expected: &[f32], actual: &[f32]) -> Result<(), String> {
if expected.len() != actual.len() {
return Err(format!(
"Shape mismatch: {} vs {}",
expected.len(),
actual.len()
));
}
let mut max_rel = 0.0f64;
let mut max_abs = 0.0f64;
let mut mismatches = 0usize;
for (i, (e, a)) in expected.iter().zip(actual.iter()).enumerate() {
let abs_diff = (e - a).abs() as f64;
let rel_diff = if e.abs() as f64 > 1e-10 {
abs_diff / e.abs() as f64
} else {
abs_diff
};
if rel_diff > max_rel {
max_rel = rel_diff;
}
if abs_diff > max_abs {
max_abs = abs_diff;
}
if rel_diff > self.max_relative_error || abs_diff > self.max_absolute_error {
mismatches += 1;
}
}
if mismatches > 0 {
Err(format!(
"{} mismatches ({}%), max_rel={:.2e}, max_abs={:.2e}",
mismatches,
mismatches * 100 / expected.len(),
max_rel,
max_abs,
))
} else {
Ok(())
}
}
pub fn verify_scalar(&self, expected: f32, actual: f32, label: &str) -> Result<(), String> {
let abs_diff = (expected - actual).abs();
let rel_diff = if expected.abs() > 1e-10 {
abs_diff / expected.abs()
} else {
abs_diff
};
if rel_diff > self.tolerance as f32 {
Err(format!(
"{}: expected {}, got {} (rel={:.2e})",
label, expected, actual, rel_diff
))
} else {
Ok(())
}
}
pub fn verify_reduce_sum(&self, data: &[f32], expected: f32) -> Result<(), String> {
let actual: f32 = data.iter().sum();
self.verify_scalar(expected, actual, "ReduceSum")
}
pub fn verify_reduce_max(&self, data: &[f32], expected: f32) -> Result<(), String> {
let actual = data.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
self.verify_scalar(expected, actual, "ReduceMax")
}
pub fn verify_softmax(&self, data: &[f32]) -> bool {
let sum: f32 = data.iter().sum();
(sum - 1.0).abs() < 1e-5
}
pub fn verify_layernorm(&self, data: &[f32]) -> bool {
let n = data.len() as f32;
if n == 0.0 {
return true;
}
let mean: f32 = data.iter().sum::<f32>() / n;
let var: f32 = data.iter().map(|x| (x - mean) * (x - mean)).sum::<f32>() / n;
(mean.abs() < 1e-5) && ((var - 1.0).abs() < 1e-4)
}
}
pub fn recommended_threads() -> usize {
std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(4)
}
pub fn align_up(size: usize, alignment: usize) -> usize {
(size + alignment - 1) & !(alignment - 1)
}
pub fn tensor_memory(shape: &TensorShape, dtype: MLDataType) -> usize {
shape.numel() * dtype.size_bytes()
}
pub fn is_amx_worthy(m: usize, n: usize, k: usize) -> bool {
m >= 64 && n >= 64 && k >= 64
}
pub fn is_vnni_worthy(m: usize, n: usize, k: usize) -> bool {
m >= 16 && n >= 16 && k >= 32
}
pub fn is_avx512_worthy(m: usize, n: usize, k: usize) -> bool {
m >= 16 || n >= 16 || k >= 16
}
pub fn recommend_isa_for_size(
m: usize,
n: usize,
k: usize,
features: &[MLTargetFeature],
) -> MLTargetFeature {
if features.contains(&MLTargetFeature::AMXINT8) && is_amx_worthy(m, n, k) {
MLTargetFeature::AMXINT8
} else if features.contains(&MLTargetFeature::AVX512VNNI) && is_vnni_worthy(m, n, k) {
MLTargetFeature::AVX512VNNI
} else if features.contains(&MLTargetFeature::AVX512F) && is_avx512_worthy(m, n, k) {
MLTargetFeature::AVX512F
} else if features.contains(&MLTargetFeature::AVX2) {
MLTargetFeature::AVX2
} else if features.contains(&MLTargetFeature::AVX) {
MLTargetFeature::AVX
} else {
MLTargetFeature::SSE2
}
}
pub fn arithmetic_intensity_gemm(m: usize, n: usize, k: usize, dtype: MLDataType) -> f64 {
let flops = 2.0 * m as f64 * n as f64 * k as f64;
let bytes = (m * k + n * k + m * n) as f64 * dtype.size_bytes() as f64;
if bytes > 0.0 {
flops / bytes
} else {
0.0
}
}
pub fn arithmetic_intensity_conv(
n: usize,
c_in: usize,
c_out: usize,
h: usize,
w: usize,
kh: usize,
kw: usize,
) -> f64 {
let flops =
2.0 * n as f64 * c_out as f64 * h as f64 * w as f64 * kh as f64 * kw as f64 * c_in as f64;
let bytes = (n * c_in * h * w + c_out * c_in * kh * kw + n * c_out * h * w) as f64 * 4.0;
if bytes > 0.0 {
flops / bytes
} else {
0.0
}
}
pub fn is_compute_bound(arithmetic_intensity: f64, peak_flops_per_byte: f64) -> bool {
arithmetic_intensity > peak_flops_per_byte
}
#[cfg(test)]
mod utility_tests {
use super::*;
#[test]
fn test_recommended_threads() {
let n = recommended_threads();
assert!(n >= 1);
}
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 64), 0);
assert_eq!(align_up(1, 64), 64);
assert_eq!(align_up(63, 64), 64);
assert_eq!(align_up(64, 64), 64);
assert_eq!(align_up(65, 64), 128);
assert_eq!(align_up(100, 16), 112);
}
#[test]
fn test_tensor_memory() {
let s = TensorShape::new(vec![1, 3, 224, 224]);
assert_eq!(tensor_memory(&s, MLDataType::FP32), 1 * 3 * 224 * 224 * 4);
assert_eq!(tensor_memory(&s, MLDataType::INT8), 1 * 3 * 224 * 224);
}
#[test]
fn test_worthy_checks() {
assert!(is_amx_worthy(64, 64, 64));
assert!(!is_amx_worthy(32, 32, 32));
assert!(is_vnni_worthy(16, 16, 32));
assert!(!is_vnni_worthy(8, 8, 16));
assert!(is_avx512_worthy(16, 1, 1));
assert!(!is_avx512_worthy(4, 4, 4));
}
#[test]
fn test_recommend_isa_for_size() {
let features = vec![
MLTargetFeature::AVX2,
MLTargetFeature::AVX512F,
MLTargetFeature::AVX512VNNI,
MLTargetFeature::AMXINT8,
];
assert_eq!(
recommend_isa_for_size(64, 64, 64, &features),
MLTargetFeature::AMXINT8
);
assert_eq!(
recommend_isa_for_size(32, 32, 64, &features),
MLTargetFeature::AVX512VNNI
);
assert_eq!(
recommend_isa_for_size(16, 16, 16, &features),
MLTargetFeature::AVX512F
);
assert_eq!(
recommend_isa_for_size(4, 4, 4, &features),
MLTargetFeature::AVX2
);
let basic = vec![MLTargetFeature::SSE2];
assert_eq!(
recommend_isa_for_size(256, 256, 256, &basic),
MLTargetFeature::SSE2
);
}
#[test]
fn test_arithmetic_intensity() {
let ai = arithmetic_intensity_gemm(256, 256, 256, MLDataType::FP32);
assert!(ai > 0.0);
assert!(ai < 1000.0);
let ai_conv = arithmetic_intensity_conv(1, 64, 128, 56, 56, 3, 3);
assert!(ai_conv > 0.0);
}
}
#[cfg(test)]
mod runtime_registry_tests {
use super::*;
#[test]
fn test_onnx_registry_default() {
let reg = ONNXOpRegistry::new();
assert_eq!(reg.lookup("MatMul"), Some(MLOpKind::MatMul));
assert_eq!(reg.lookup("Conv"), Some(MLOpKind::Conv2D));
assert_eq!(reg.lookup("Relu"), Some(MLOpKind::ReLU));
assert_eq!(reg.lookup("Gelu"), Some(MLOpKind::GELU));
assert_eq!(reg.lookup("Silu"), Some(MLOpKind::SiLU));
assert_eq!(reg.lookup("Softmax"), Some(MLOpKind::Softmax));
assert_eq!(reg.lookup("BatchNormalization"), Some(MLOpKind::BatchNorm));
assert_eq!(reg.lookup("LayerNormalization"), Some(MLOpKind::LayerNorm));
assert_eq!(reg.lookup("Nonexistent"), None);
}
#[test]
fn test_onnx_registry_custom() {
let mut reg = ONNXOpRegistry::new();
reg.register_custom("MyCustomOp", MLOpKind::GELU);
assert_eq!(reg.lookup("MyCustomOp"), Some(MLOpKind::GELU));
}
#[test]
fn test_tflite_registry() {
let reg = TFLiteOpRegistry::new();
assert_eq!(reg.lookup(3), Some(MLOpKind::Conv2D));
assert_eq!(reg.lookup(9), Some(MLOpKind::MatMul));
assert_eq!(reg.lookup(14), Some(MLOpKind::Softmax));
assert_eq!(reg.lookup(32), Some(MLOpKind::GELU));
assert_eq!(reg.lookup(999), None);
}
#[test]
fn test_platform_detection() {
let plat = X86MLPlatform::detect();
assert!(!plat.platform_name.is_empty());
assert!(plat.recommended_config.opt_level >= MLOptLevel::O0);
let summary = plat.platform_summary();
assert!(summary.contains("Platform:"));
}
#[test]
fn test_platform_create_ml() {
let plat = X86MLPlatform::detect();
let ml = plat.create_ml_support();
assert!(ml.has_feature(MLTargetFeature::AVX2));
}
}
#[cfg(test)]
mod verifier_tests {
use super::*;
#[test]
fn test_verify_tensor_exact() {
let v = X86MLVerifier::default();
let data = vec![1.0, 2.0, 3.0];
assert!(v.verify_tensor(&data, &data).is_ok());
}
#[test]
fn test_verify_tensor_close() {
let v = X86MLVerifier::new(1e-3);
let expected = vec![1.0, 2.0, 3.0];
let actual = vec![1.0001, 1.9999, 3.00005];
assert!(v.verify_tensor(&expected, &actual).is_ok());
}
#[test]
fn test_verify_tensor_mismatch() {
let v = X86MLVerifier::new(1e-6);
let expected = vec![1.0, 2.0, 3.0];
let actual = vec![1.0, 99.0, 3.0];
assert!(v.verify_tensor(&expected, &actual).is_err());
}
#[test]
fn test_verify_tensor_size_mismatch() {
let v = X86MLVerifier::default();
assert!(v.verify_tensor(&[1.0, 2.0], &[1.0]).is_err());
}
#[test]
fn test_verify_softmax() {
let v = X86MLVerifier::default();
let softmax_output = vec![0.1, 0.2, 0.3, 0.4];
assert!(v.verify_softmax(&softmax_output));
let bad = vec![1.0, 2.0, 3.0];
assert!(!v.verify_softmax(&bad));
}
#[test]
fn test_verify_layernorm() {
let v = X86MLVerifier::default();
let normalized = vec![-1.22474487, 0.0, 1.22474487];
assert!(v.verify_layernorm(&normalized));
let bad = vec![1.0, 2.0, 3.0];
assert!(!v.verify_layernorm(&bad));
}
#[test]
fn test_verify_scalar() {
let v = X86MLVerifier::new(1e-4);
assert!(v.verify_scalar(1.0, 1.00001, "test").is_ok());
assert!(v.verify_scalar(1.0, 2.0, "test").is_err());
}
}
pub fn module_documentation() -> &'static str {
"X86 ML Compiler Support — complete documentation in module header."
}
pub struct X86MLWeightsPacker {
pub feature: MLTargetFeature,
}
#[derive(Debug, Clone)]
pub struct PackedWeights {
pub original_shape: Vec<usize>,
pub packed_shape: Vec<usize>,
pub dtype: MLDataType,
pub layout: MLLayoutFormat,
pub data: Vec<u8>,
}
impl X86MLWeightsPacker {
pub fn new(feature: MLTargetFeature) -> Self {
Self { feature }
}
pub fn pack_vnni_int8(&self, weights: &[i8], k: usize, n: usize) -> PackedWeights {
let vnni_block = 4; let k_padded = align_up(k, vnni_block);
let n_padded = align_up(n, 64); let mut packed = vec![0i8; k_padded * n_padded];
for nb in (0..n).step_by(64) {
for kb in (0..k).step_by(vnni_block) {
for ni in 0..64.min(n - nb) {
for ki in 0..vnni_block.min(k - kb) {
let src_idx = (kb + ki) * n + (nb + ni);
let dst_idx = (nb / 64) * k_padded * 64
+ (kb / vnni_block) * 64 * vnni_block
+ ni * vnni_block
+ ki;
if src_idx < weights.len() && dst_idx < packed.len() {
packed[dst_idx] = weights[src_idx];
}
}
}
}
}
PackedWeights {
original_shape: vec![k, n],
packed_shape: vec![k_padded / vnni_block, n_padded / 64, 64, vnni_block],
dtype: MLDataType::INT8,
layout: MLLayoutFormat::VNNIPacked {
k_block: 4,
n_block: 64,
},
data: packed.iter().map(|&x| x as u8).collect(),
}
}
pub fn pack_bf16(&self, weights: &[u16], k: usize, n: usize) -> PackedWeights {
let bf16_pair = 2;
let k_padded = align_up(k, bf16_pair * 16); let n_padded = align_up(n, 16);
let mut packed = vec![0u16; k_padded * n_padded];
for nb in (0..n).step_by(16) {
for kb in (0..k).step_by(bf16_pair) {
for ni in 0..16.min(n - nb) {
for ki in 0..bf16_pair.min(k - kb) {
let src = (kb + ki) * n + (nb + ni);
let dst = (nb / 16) * k_padded * 16
+ (kb / bf16_pair) * 16 * bf16_pair
+ ni * bf16_pair
+ ki;
if src < weights.len() && dst < packed.len() {
packed[dst] = weights[src];
}
}
}
}
}
let data: Vec<u8> = packed.iter().flat_map(|&x| x.to_le_bytes()).collect();
PackedWeights {
original_shape: vec![k, n],
packed_shape: vec![k_padded / bf16_pair, n_padded / 16, 16, bf16_pair],
dtype: MLDataType::BF16,
layout: MLLayoutFormat::BF16Packed {
k_block: 2,
n_block: 16,
},
data,
}
}
pub fn pack_amx_tile(
&self,
weights: &[i8],
k: usize,
n: usize,
rows: usize,
col_bytes: usize,
) -> PackedWeights {
let k_padded = align_up(k, rows);
let n_padded = align_up(n, col_bytes);
let mut packed = vec![0i8; k_padded * n_padded];
for r in 0..k {
let src_offset = r * n;
let dst_offset = r * n_padded;
packed[dst_offset..dst_offset + n]
.copy_from_slice(&weights[src_offset..src_offset + n]);
}
PackedWeights {
original_shape: vec![k, n],
packed_shape: vec![k_padded, n_padded],
dtype: MLDataType::INT8,
layout: MLLayoutFormat::AMXTile { rows, col_bytes },
data: packed.iter().map(|&x| x as u8).collect(),
}
}
pub fn transpose_pack(&self, data: &[f32], rows: usize, cols: usize) -> Vec<f32> {
let mut transposed = vec![0.0f32; rows * cols];
for r in 0..rows {
for c in 0..cols {
transposed[c * rows + r] = data[r * cols + c];
}
}
transposed
}
pub fn block_pack(
&self,
data: &[f32],
rows: usize,
cols: usize,
block_m: usize,
block_n: usize,
) -> Vec<f32> {
let mut blocked = vec![0.0f32; rows * cols];
let m_blocks = (rows + block_m - 1) / block_m;
let n_blocks = (cols + block_n - 1) / block_n;
for mb in 0..m_blocks {
for nb in 0..n_blocks {
for mi in 0..block_m {
let r = mb * block_m + mi;
if r >= rows {
break;
}
for ni in 0..block_n {
let c = nb * block_n + ni;
if c >= cols {
break;
}
let src_idx = r * cols + c;
let dst_idx = (mb * n_blocks + nb) * block_m * block_n + mi * block_n + ni;
blocked[dst_idx] = data[src_idx];
}
}
}
}
blocked
}
}
#[derive(Debug, Clone)]
pub struct MLBuffer {
pub name: String,
pub shape: TensorShape,
pub dtype: MLDataType,
pub layout: MLLayoutFormat,
pub data: Vec<u8>,
pub is_external: bool,
}
impl MLBuffer {
pub fn new(name: &str, shape: TensorShape, dtype: MLDataType) -> Self {
let size = shape.numel() * dtype.size_bytes();
Self {
name: name.to_string(),
shape,
dtype,
layout: MLLayoutFormat::RowMajor,
data: vec![0u8; size],
is_external: false,
}
}
pub fn from_slice_f32(name: &str, shape: TensorShape, data: &[f32]) -> Self {
let mut buf = Self::new(name, shape, MLDataType::FP32);
let bytes: &[u8] = bytemuck::cast_slice(data);
buf.data.copy_from_slice(bytes);
buf
}
pub fn as_f32_slice(&self) -> &[f32] {
bytemuck::cast_slice(&self.data)
}
pub fn as_f32_mut(&mut self) -> &mut [f32] {
bytemuck::cast_slice_mut(&mut self.data)
}
pub fn numel(&self) -> usize {
self.shape.numel()
}
pub fn size_bytes(&self) -> usize {
self.data.len()
}
pub fn reshape(&mut self, new_shape: TensorShape) {
assert_eq!(
new_shape.numel(),
self.shape.numel(),
"Reshape must preserve element count"
);
self.shape = new_shape;
}
pub fn zero(&mut self) {
self.data.fill(0);
}
pub fn fill_f32(&mut self, value: f32) {
for elem in self.as_f32_mut() {
*elem = value;
}
}
pub fn random_uniform(&mut self, min: f32, max: f32) {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
let mut state = seed;
for elem in self.as_f32_mut() {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let r = (state >> 32) as f32 / (u32::MAX as f32);
*elem = min + r * (max - min);
}
}
}
#[derive(Debug, Clone)]
pub struct MLBufferManager {
pub buffers: HashMap<String, MLBuffer>,
pub total_allocated: usize,
}
impl MLBufferManager {
pub fn new() -> Self {
Self {
buffers: HashMap::new(),
total_allocated: 0,
}
}
pub fn allocate(&mut self, name: &str, shape: TensorShape, dtype: MLDataType) -> &MLBuffer {
let buf = MLBuffer::new(name, shape, dtype);
self.total_allocated += buf.size_bytes();
self.buffers.insert(name.to_string(), buf);
self.buffers.get(name).unwrap()
}
pub fn get(&self, name: &str) -> Option<&MLBuffer> {
self.buffers.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut MLBuffer> {
self.buffers.get_mut(name)
}
pub fn release(&mut self, name: &str) -> Option<MLBuffer> {
if let Some(buf) = self.buffers.remove(name) {
self.total_allocated = self.total_allocated.saturating_sub(buf.size_bytes());
Some(buf)
} else {
None
}
}
pub fn release_all(&mut self) {
self.buffers.clear();
self.total_allocated = 0;
}
}
impl Default for MLBufferManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod packer_buffer_tests {
use super::*;
#[test]
fn test_pack_vnni_int8() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX512VNNI);
let weights: Vec<i8> = (0..256i16).map(|i| (i % 127) as i8).collect(); let packed = packer.pack_vnni_int8(&weights, 16, 16);
assert!(!packed.data.is_empty());
assert_eq!(packed.dtype, MLDataType::INT8);
}
#[test]
fn test_pack_bf16() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX512BF16);
let weights: Vec<u16> = (0..256).map(|i| i as u16).collect();
let packed = packer.pack_bf16(&weights, 16, 16);
assert!(!packed.data.is_empty());
}
#[test]
fn test_pack_amx_tile() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AMXINT8);
let weights: Vec<i8> = vec![1i8; 16 * 64];
let packed = packer.pack_amx_tile(&weights, 16, 64, 16, 64);
assert!(!packed.data.is_empty());
}
#[test]
fn test_transpose_pack() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX2);
let data: Vec<f32> = (0..12).map(|i| i as f32).collect(); let transposed = packer.transpose_pack(&data, 3, 4);
assert_eq!(transposed[0], 0.0);
assert_eq!(transposed[3], 3.0);
assert_eq!(transposed[1 * 3 + 0], data[0 * 4 + 1]);
}
#[test]
fn test_block_pack() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX2);
let data: Vec<f32> = (0..64).map(|i| i as f32).collect(); let blocked = packer.block_pack(&data, 8, 8, 4, 4);
assert_eq!(blocked.len(), 64);
assert_eq!(blocked[0], 0.0);
}
#[test]
fn test_ml_buffer_creation() {
let buf = MLBuffer::new("test", TensorShape::new(vec![2, 3]), MLDataType::FP32);
assert_eq!(buf.name, "test");
assert_eq!(buf.numel(), 6);
assert_eq!(buf.size_bytes(), 24);
}
#[test]
fn test_ml_buffer_from_f32() {
let buf =
MLBuffer::from_slice_f32("data", TensorShape::new(vec![4]), &[1.0, 2.0, 3.0, 4.0]);
assert_eq!(buf.as_f32_slice(), &[1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn test_ml_buffer_reshape() {
let mut buf = MLBuffer::from_slice_f32(
"r",
TensorShape::new(vec![2, 3]),
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
);
buf.reshape(TensorShape::new(vec![3, 2]));
assert_eq!(buf.shape.dims, vec![3, 2]);
}
#[test]
#[should_panic]
fn test_ml_buffer_reshape_panic() {
let mut buf = MLBuffer::new("r", TensorShape::new(vec![2, 3]), MLDataType::FP32);
buf.reshape(TensorShape::new(vec![2, 2])); }
#[test]
fn test_ml_buffer_zero_fill() {
let mut buf = MLBuffer::new("z", TensorShape::new(vec![8]), MLDataType::FP32);
buf.fill_f32(42.0);
assert!(buf.as_f32_slice().iter().all(|&x| (x - 42.0).abs() < 1e-6));
}
#[test]
fn test_buffer_manager_allocate_release() {
let mut mgr = MLBufferManager::new();
mgr.allocate("a", TensorShape::new(vec![4, 4]), MLDataType::FP32);
mgr.allocate("b", TensorShape::new(vec![8, 8]), MLDataType::FP32);
assert_eq!(mgr.buffers.len(), 2);
let released = mgr.release("a");
assert!(released.is_some());
assert_eq!(mgr.buffers.len(), 1);
mgr.release_all();
assert!(mgr.buffers.is_empty());
assert_eq!(mgr.total_allocated, 0);
}
}
pub struct X86MLPipeline {
pub config: X86MLConfig,
pub platform: X86MLPlatform,
pub ml_support: X86MLSupport,
pub profiler: X86MLProfiler,
pub optimizer: X86MLOptimizer,
pub codegen: X86MLCodegen,
pub transform: X86MLTransform,
}
impl X86MLPipeline {
pub fn new(config: X86MLConfig) -> Self {
let platform = X86MLPlatform::detect();
let ml_support = config.build();
let optimizer = X86MLOptimizer::new(config.target_features.clone(), config.opt_level);
let codegen = X86MLCodegen::new(
config
.target_features
.iter()
.max_by_key(|f| f.rank())
.copied()
.unwrap_or(MLTargetFeature::SSE2),
);
let transform = X86MLTransform::new(config.opt_level);
Self {
config,
platform,
ml_support,
profiler: X86MLProfiler::new(),
optimizer,
codegen,
transform,
}
}
pub fn compile_model(&mut self, model: &X86MLModel) -> Result<CompiledModel, MLError> {
let mut graph = model.graph.clone();
self.transform.optimize(&mut graph);
for node in &graph.nodes {
match node.op {
MLOpKind::MatMul => {
let m = node.shape.dim(0).unwrap_or(1);
let k = node.shape.dim(1).unwrap_or(1);
let n = node.shape.numel() / m;
self.optimizer.optimize_gemm(m, n, k, node.dtype);
}
MLOpKind::Conv2D => {
let (n, c_in, h, w) = node.shape.as_nchw().unwrap_or((1, 1, 1, 1));
self.optimizer.optimize_conv(n, c_in, 64, h, w, 3, 3);
}
_ => {}
}
}
let mut ops = Vec::new();
for node in &graph.nodes {
let result =
self.ml_support
.lower_tensor_op(node.op, &node.shape, &node.shape, &node.shape)?;
ops.push(result);
}
for op_result in &ops {
if op_result.op == MLOpKind::MatMul {
self.codegen.emit_gemm_kernel(
&op_result.kernel_name,
64,
128,
256,
MLDataType::FP32,
);
}
}
Ok(CompiledModel {
name: model.name.clone(),
format: model.format,
ops,
total_flops: model.estimate_flops(),
memory_bytes: model.model_size_bytes(),
})
}
pub fn profile_model(&mut self, model: &CompiledModel) {
self.profiler.start_session(&model.name);
for op in &model.ops {
self.profiler.record(
op.op.name(),
&op.kernel_name,
model.estimate_inference_time_us(3.0) / model.ops.len() as f64,
op.flops,
op.bytes_read,
op.bytes_written,
);
}
self.profiler.end_session();
}
pub fn emit_assembly(&self, model: &CompiledModel) -> String {
let mut asm = String::new();
asm.push_str(&format!(
"# Auto-generated X86 ML assembly for: {}\n",
model.name
));
asm.push_str(&format!("# Total FLOPS: {}\n", model.total_flops));
asm.push_str(&format!("# Memory: {} bytes\n", model.memory_bytes));
asm.push_str("# Operations:\n");
for op in &model.ops {
asm.push_str(&format!(
"# {}: {} ({} FLOPS)\n",
op.op.name(),
op.kernel_name,
op.flops
));
}
asm.push_str(&self.codegen.dump());
asm.push('\n');
asm
}
pub fn report(&self) -> String {
format!(
"{}\n\n{}\n\n{}",
self.platform.platform_summary(),
self.ml_support.summary(),
self.profiler.report(),
)
}
}
#[cfg(test)]
mod pipeline_tests {
use super::*;
#[test]
fn test_pipeline_creation() {
let config = X86MLConfig::default();
let pipeline = X86MLPipeline::new(config);
assert!(pipeline.ml_support.has_feature(MLTargetFeature::AVX2));
}
#[test]
fn test_pipeline_compile_simple() {
let config = X86MLConfig::default();
let mut pipeline = X86MLPipeline::new(config);
let mut model = X86MLModel::new("test", ModelFormat::Raw, X86MLConfig::default());
model.graph.add_node(MLGraphNode {
id: 0,
op: MLOpKind::ReLU,
inputs: vec![],
outputs: vec![],
shape: TensorShape::new(vec![1, 64]),
dtype: MLDataType::FP32,
attributes: HashMap::new(),
});
let result = pipeline.compile_model(&model);
assert!(result.is_ok());
}
#[test]
fn test_pipeline_profile() {
let mut pipeline = X86MLPipeline::new(X86MLConfig::default());
let model = CompiledModel {
name: "p".into(),
format: ModelFormat::Raw,
ops: vec![TensorOpResult {
op: MLOpKind::MatMul,
kernel_name: "gemm_test".into(),
flops: 1000,
bytes_read: 100,
bytes_written: 50,
vectorized: true,
isa_used: Some(MLTargetFeature::AVX2),
block_sizes: Some((64, 64, 64)),
}],
total_flops: 1000,
memory_bytes: 1024,
};
pipeline.profile_model(&model);
let report = pipeline.profiler.report();
assert!(report.contains("p"));
}
#[test]
fn test_pipeline_emit_assembly() {
let pipeline = X86MLPipeline::new(X86MLConfig::default());
let model = CompiledModel {
name: "asm_test".into(),
format: ModelFormat::Raw,
ops: vec![],
total_flops: 0,
memory_bytes: 0,
};
let asm = pipeline.emit_assembly(&model);
assert!(asm.contains("asm_test"));
}
#[test]
fn test_pipeline_report() {
let pipeline = X86MLPipeline::new(X86MLConfig::default());
let report = pipeline.report();
assert!(report.contains("Platform:"));
}
#[test]
fn test_weights_packer_vnni_full() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX512VNNI);
let k = 256usize;
let n = 256usize;
let weights: Vec<i8> = (0..(k * n)).map(|i| (i % 255 - 127) as i8).collect();
let packed = packer.pack_vnni_int8(&weights, k, n);
assert!(!packed.data.is_empty());
assert!(packed.packed_shape[0] >= k / 4);
assert!(packed.packed_shape[1] >= n / 64);
}
#[test]
fn test_weights_packer_bf16_full() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AVX512BF16);
let k = 128usize;
let n = 128usize;
let weights: Vec<u16> = vec![0x3F80u16; k * n]; let packed = packer.pack_bf16(&weights, k, n);
assert!(!packed.data.is_empty());
}
#[test]
fn test_weights_packer_amx_full() {
let packer = X86MLWeightsPacker::new(MLTargetFeature::AMXINT8);
let rows = 16usize;
let col_bytes = 64usize;
let weights: Vec<i8> = vec![0i8; rows * col_bytes];
let packed = packer.pack_amx_tile(&weights, rows, col_bytes, rows, col_bytes);
assert_eq!(packed.data.len(), rows * col_bytes);
}
#[test]
fn test_buffer_random_uniform() {
let mut buf = MLBuffer::new("rand", TensorShape::new(vec![1000]), MLDataType::FP32);
buf.random_uniform(-1.0, 1.0);
let slice = buf.as_f32_slice();
assert!(slice.iter().all(|&x| x >= -1.0 && x <= 1.0));
}
#[test]
fn test_buffer_manager_memory_tracking() {
let mut mgr = MLBufferManager::new();
mgr.allocate("x", TensorShape::new(vec![1024, 1024]), MLDataType::FP32);
assert_eq!(mgr.total_allocated, 1024 * 1024 * 4);
mgr.allocate("y", TensorShape::new(vec![512, 512]), MLDataType::FP16);
assert_eq!(mgr.total_allocated, 1024 * 1024 * 4 + 512 * 512 * 2);
}
#[test]
fn test_arithmetic_intensity_compute_bound() {
let ai = arithmetic_intensity_gemm(1024, 1024, 1024, MLDataType::FP32);
let peak_flops_per_byte = 50.0; assert!(is_compute_bound(ai, peak_flops_per_byte));
let ai_low = arithmetic_intensity_gemm(4, 4, 4, MLDataType::FP32);
assert!(!is_compute_bound(ai_low, peak_flops_per_byte));
}
}
pub type MLResult<T> = Result<T, MLError>;
pub enum VectorReg {
Xmm(String),
Ymm(String),
Zmm(String),
Tmm(String),
}
impl VectorReg {
pub fn name(&self) -> &str {
match self {
VectorReg::Xmm(s) | VectorReg::Ymm(s) | VectorReg::Zmm(s) | VectorReg::Tmm(s) => s,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConvAlgorithm {
Auto,
Im2ColGEMM,
Winograd,
Direct,
FFT,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GEMMAlgorithm {
Auto,
Naive,
Blocked,
Packed,
VNNI,
AMX,
BF16Packed,
FP16Packed,
}
impl Default for GEMMAlgorithm {
fn default() -> Self {
GEMMAlgorithm::Auto
}
}
#[derive(Debug, Clone)]
pub struct CompilationCacheEntry {
pub hash: u64,
pub kernel_name: String,
pub asm_code: String,
pub compile_time_us: u64,
}
pub struct MLCompilationCache {
pub entries: HashMap<u64, CompilationCacheEntry>,
pub max_entries: usize,
}
impl MLCompilationCache {
pub fn new(max_entries: usize) -> Self {
Self {
entries: HashMap::new(),
max_entries,
}
}
pub fn lookup(&self, hash: u64) -> Option<&CompilationCacheEntry> {
self.entries.get(&hash)
}
pub fn insert(&mut self, entry: CompilationCacheEntry) {
if self.entries.len() >= self.max_entries {
if let Some(key) = self.entries.keys().next().copied() {
self.entries.remove(&key);
}
}
self.entries.insert(entry.hash, entry);
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
impl Default for MLCompilationCache {
fn default() -> Self {
Self::new(1024)
}
}
pub fn hash_kernel_params(
op: MLOpKind,
m: usize,
n: usize,
k: usize,
dtype: MLDataType,
isa: MLTargetFeature,
) -> u64 {
let mut h: u64 = 0;
h = h.wrapping_mul(31).wrapping_add(op as u64);
h = h.wrapping_mul(31).wrapping_add(m as u64);
h = h.wrapping_mul(31).wrapping_add(n as u64);
h = h.wrapping_mul(31).wrapping_add(k as u64);
h = h.wrapping_mul(31).wrapping_add(dtype.size_bits() as u64);
h = h.wrapping_mul(31).wrapping_add(isa.rank() as u64);
h
}
#[cfg(test)]
mod final_section_tests {
use super::*;
#[test]
fn test_vector_reg_names() {
let xmm = VectorReg::Xmm("%xmm0".into());
assert_eq!(xmm.name(), "%xmm0");
}
#[test]
fn test_conv_algorithm_defaults() {
assert_eq!(GEMMAlgorithm::default(), GEMMAlgorithm::Auto);
}
#[test]
fn test_hash_kernel_params() {
let h1 = hash_kernel_params(
MLOpKind::MatMul,
64,
128,
256,
MLDataType::FP32,
MLTargetFeature::AVX2,
);
let h2 = hash_kernel_params(
MLOpKind::MatMul,
64,
128,
256,
MLDataType::FP32,
MLTargetFeature::AVX2,
);
assert_eq!(h1, h2);
let h3 = hash_kernel_params(
MLOpKind::MatMul,
64,
128,
256,
MLDataType::FP32,
MLTargetFeature::AVX512F,
);
assert_ne!(h1, h3);
}
#[test]
fn test_compilation_cache() {
let mut cache = MLCompilationCache::new(10);
let entry = CompilationCacheEntry {
hash: 12345,
kernel_name: "test_kernel".into(),
asm_code: "vmovups".into(),
compile_time_us: 100,
};
cache.insert(entry);
assert!(cache.lookup(12345).is_some());
assert!(cache.lookup(99999).is_none());
cache.clear();
assert!(cache.lookup(12345).is_none());
}
}