use onnx_runtime_ir::Attribute;
use crate::context::InferenceContext;
use crate::dim_expr::DimExpr;
use crate::error::ShapeInferError;
use crate::registry::InferenceRegistry;
pub fn matmul(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let a = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let b = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
if let (Some(a), Some(b), Some(dtype)) = (a, b, dtype) {
let shape = matmul_shape(ctx, &a, &b, "MatMul")?;
ctx.set_output(0, dtype, shape);
}
Ok(())
}
pub fn quantized_matmul(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(mut shape) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) else {
return Ok(());
};
let Some(dtype) = ctx.input_dtype(0) else {
return Ok(());
};
let n = ctx
.node
.attr("N")
.and_then(Attribute::as_int)
.ok_or_else(|| ShapeInferError::MissingAttribute {
op: ctx.node.op_type.clone(),
attr: "N".into(),
})?;
let Some(last) = shape.last_mut() else {
return Err(ShapeInferError::InvalidRank {
op: ctx.node.op_type.clone(),
index: 0,
rank: 0,
detail: "activation input must have rank ≥ 1".into(),
});
};
*last = DimExpr::constant(n);
ctx.set_output(0, dtype, shape);
Ok(())
}
pub fn fused_matmul(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let a = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let b = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(a), Some(b), Some(dtype)) = (a, b, dtype) else {
return Ok(());
};
let flag = |name: &str| {
ctx.node
.attr(name)
.and_then(Attribute::as_int)
.unwrap_or(0)
!= 0
};
let a_eff = apply_fused_trans(&a, flag("transA"), flag("transBatchA"));
let b_eff = apply_fused_trans(&b, flag("transB"), flag("transBatchB"));
let shape = matmul_shape(ctx, &a_eff, &b_eff, "FusedMatMul")?;
ctx.set_output(0, dtype, shape);
Ok(())
}
fn apply_fused_trans(raw: &[DimExpr], trans: bool, trans_batch: bool) -> Vec<DimExpr> {
let rank = raw.len();
if rank < 2 {
return raw.to_vec();
}
let mut out = Vec::with_capacity(rank);
let (start, end) = if trans_batch {
(1, rank - 1)
} else {
(0, rank - 2)
};
out.extend_from_slice(&raw[start..end]);
let row_idx = if trans {
rank - 1
} else if trans_batch {
0
} else {
rank - 2
};
let col_idx = if trans {
if trans_batch { 0 } else { rank - 2 }
} else {
rank - 1
};
out.push(raw[row_idx].clone());
out.push(raw[col_idx].clone());
out
}
fn matmul_shape(
ctx: &mut InferenceContext,
a: &[DimExpr],
b: &[DimExpr],
op: &str,
) -> Result<Vec<DimExpr>, ShapeInferError> {
if a.is_empty() || b.is_empty() {
return Err(ShapeInferError::Invalid {
op: op.into(),
detail: "operands must have rank ≥ 1".into(),
});
}
let a_is_1d = a.len() == 1;
let b_is_1d = b.len() == 1;
let a2: Vec<DimExpr> = if a_is_1d {
vec![DimExpr::constant(1), a[0].clone()]
} else {
a.to_vec()
};
let b2: Vec<DimExpr> = if b_is_1d {
vec![b[0].clone(), DimExpr::constant(1)]
} else {
b.to_vec()
};
let (a_batch, a_mk) = a2.split_at(a2.len() - 2);
let (b_batch, b_kn) = b2.split_at(b2.len() - 2);
let m = a_mk[0].clone();
let ka = a_mk[1].clone();
let kb = b_kn[0].clone();
let n = b_kn[1].clone();
if let (Some(ka), Some(kb)) = (ka.as_const(), kb.as_const())
&& ka != kb
{
return Err(ShapeInferError::Invalid {
op: op.into(),
detail: format!("contraction mismatch: {ka} vs {kb}"),
});
}
let mut shape = ctx.broadcast(a_batch, b_batch)?;
if !a_is_1d {
shape.push(m);
}
if !b_is_1d {
shape.push(n);
}
Ok(shape)
}
pub fn fused_matmul_bias(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let a = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let b = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
if let (Some(a), Some(b), Some(dtype)) = (a, b, dtype) {
let shape = matmul_shape(ctx, &a, &b, "FusedMatMulBias")?;
ctx.set_output(0, dtype, shape);
}
Ok(())
}
pub fn fused_gemm(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let a = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let b = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
if let (Some(a), Some(b), Some(dtype)) = (a, b, dtype) {
let shape = matmul_shape(ctx, &a, &b, "FusedGemm")?;
ctx.set_output(0, dtype, shape);
}
Ok(())
}
pub fn fused_attention(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let q = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let k = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let v = ctx.input_shape(2).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(q), Some(k), Some(v), Some(dtype)) = (q, k, v, dtype) else {
return Ok(());
};
let k_transposed = ctx
.node
.attr("k_transposed")
.and_then(Attribute::as_int)
.unwrap_or(0)
!= 0;
let k_eff = if k_transposed {
k
} else if k.len() >= 2 {
let mut kt = k.clone();
let r = kt.len();
kt.swap(r - 2, r - 1);
kt
} else {
k
};
let scores = matmul_shape(ctx, &q, &k_eff, "FusedAttention")?;
let shape = matmul_shape(ctx, &scores, &v, "FusedAttention")?;
ctx.set_output(0, dtype, shape);
Ok(())
}
pub fn attention(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let q = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let k = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let v = ctx.input_shape(2).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(q), Some(k), Some(v), Some(dtype)) = (q, k, v, dtype) else {
return Ok(());
};
let q_rank = q.len();
if !(q_rank == 3 || q_rank == 4) || k.len() != q_rank || v.len() != q_rank {
return Err(ShapeInferError::Invalid {
op: "Attention".into(),
detail: format!(
"Q, K, V must all be rank 3 or 4 (got Q={q_rank}, K={}, V={})",
k.len(),
v.len()
),
});
}
let attr_dim = |name: &str| -> Option<DimExpr> {
ctx.node
.attr(name)
.and_then(Attribute::as_int)
.map(DimExpr::constant)
};
let batch = q[0].clone();
let (q_heads, q_seq, head_size, kv_heads, kv_seq, v_head_size) = if q_rank == 4 {
(
q[1].clone(),
q[2].clone(),
q[3].clone(),
k[1].clone(),
k[2].clone(),
v[3].clone(),
)
} else {
let q_heads = attr_dim("q_num_heads").ok_or_else(|| ShapeInferError::Invalid {
op: "Attention".into(),
detail: "3D inputs require the `q_num_heads` attribute".into(),
})?;
let kv_heads = attr_dim("kv_num_heads").ok_or_else(|| ShapeInferError::Invalid {
op: "Attention".into(),
detail: "3D inputs require the `kv_num_heads` attribute".into(),
})?;
let head_size = q[2]
.checked_div(&q_heads)
.unwrap_or_else(|| ctx.fresh_dim());
let v_head_size = v[2]
.checked_div(&kv_heads)
.unwrap_or_else(|| ctx.fresh_dim());
(
q_heads,
q[1].clone(),
head_size,
kv_heads,
k[1].clone(),
v_head_size,
)
};
let total_seq = match ctx.input_shape(4) {
Some(pk) if pk.len() == 4 => pk[2].add(&kv_seq),
_ => kv_seq.clone(),
};
let y_shape = if q_rank == 4 {
vec![
batch.clone(),
q_heads.clone(),
q_seq.clone(),
v_head_size.clone(),
]
} else {
vec![
batch.clone(),
q_seq.clone(),
q_heads.mul(&v_head_size),
]
};
ctx.set_output(0, dtype, y_shape);
if ctx.num_outputs() > 1 {
ctx.set_output(
1,
dtype,
vec![
batch.clone(),
kv_heads.clone(),
total_seq.clone(),
head_size.clone(),
],
);
}
if ctx.num_outputs() > 2 {
let v_dtype = ctx.input_dtype(2).unwrap_or(dtype);
ctx.set_output(
2,
v_dtype,
vec![
batch.clone(),
kv_heads.clone(),
total_seq.clone(),
v_head_size.clone(),
],
);
}
if ctx.num_outputs() > 3 {
ctx.set_output(
3,
dtype,
vec![batch, q_heads, q_seq, total_seq],
);
}
Ok(())
}
pub fn gemm(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let a = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let b = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(a), Some(b), Some(dtype)) = (a, b, dtype) else {
return Ok(());
};
if a.len() != 2 || b.len() != 2 {
return Err(ShapeInferError::InvalidRank {
op: "Gemm".into(),
index: if a.len() != 2 { 0 } else { 1 },
rank: if a.len() != 2 { a.len() } else { b.len() },
detail: "Gemm operands must be rank-2".into(),
});
}
let trans_a = ctx
.node
.attr("transA")
.and_then(|x| x.as_int())
.unwrap_or(0)
!= 0;
let trans_b = ctx
.node
.attr("transB")
.and_then(|x| x.as_int())
.unwrap_or(0)
!= 0;
let m = if trans_a { a[1].clone() } else { a[0].clone() };
let n = if trans_b { b[0].clone() } else { b[1].clone() };
ctx.set_output(0, dtype, vec![m, n]);
Ok(())
}
pub fn register(reg: &mut InferenceRegistry) {
reg.register("", "MatMul", 1, matmul);
reg.register("", "Gemm", 1, gemm);
reg.register(
"pkg.nxrt",
"BlockQuantizedMatMul",
1,
quantized_matmul,
);
reg.register("com.microsoft", "MatMulNBits", 1, quantized_matmul);
reg.register("com.microsoft", "FusedMatMul", 1, fused_matmul);
reg.register("com.microsoft", "FusedMatMulBias", 1, fused_matmul_bias);
reg.register("com.microsoft", "FusedGemm", 1, fused_gemm);
reg.register("com.microsoft", "FusedAttention", 1, fused_attention);
reg.register("", "Attention", 23, attention);
}