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 msft_attention(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let input = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let weights = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(input), Some(weights), Some(dtype)) = (input, weights, dtype) else {
return Ok(());
};
if input.len() != 3 || weights.len() != 2 {
return Err(ShapeInferError::Invalid {
op: "Attention".into(),
detail: format!(
"input must be rank 3 and weights rank 2 (got input={}, weights={})",
input.len(),
weights.len()
),
});
}
let num_heads = ctx
.node
.attr("num_heads")
.and_then(Attribute::as_int)
.map(DimExpr::constant)
.ok_or_else(|| ShapeInferError::Invalid {
op: "Attention".into(),
detail: "missing required `num_heads` attribute".into(),
})?;
let (q_hidden, v_hidden) = match ctx
.node
.attr("qkv_hidden_sizes")
.and_then(Attribute::as_ints)
{
Some(sizes) if sizes.len() == 3 => {
(DimExpr::constant(sizes[0]), DimExpr::constant(sizes[2]))
}
Some(sizes) => {
return Err(ShapeInferError::Invalid {
op: "Attention".into(),
detail: format!(
"qkv_hidden_sizes must contain 3 elements, got {}",
sizes.len()
),
});
}
None => {
let hidden = weights[1]
.checked_div(&DimExpr::constant(3))
.unwrap_or_else(|| ctx.fresh_dim());
(hidden.clone(), hidden)
}
};
let batch = input[0].clone();
let sequence = input[1].clone();
ctx.set_output(0, dtype, vec![batch.clone(), sequence.clone(), v_hidden]);
if ctx.num_outputs() > 1 {
let head_size = q_hidden
.checked_div(&num_heads)
.unwrap_or_else(|| ctx.fresh_dim());
let present_shape = match ctx.input_shape(4).map(<[DimExpr]>::to_vec) {
Some(past) if past.len() == 5 => {
let share_buffer = ctx
.node
.attr("past_present_share_buffer")
.and_then(Attribute::as_int)
.unwrap_or(0)
!= 0;
let total_sequence = if share_buffer {
past[3].clone()
} else if past[3].as_const().is_some() && sequence.as_const().is_some() {
past[3].add(&sequence)
} else {
ctx.fresh_dim()
};
vec![
DimExpr::constant(2),
batch,
num_heads,
total_sequence,
head_size,
]
}
_ => vec![DimExpr::constant(2), batch, num_heads, sequence, head_size],
};
ctx.set_output(1, dtype, present_shape);
}
Ok(())
}
pub fn multi_head_attention(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let query = ctx.input_shape(0).map(<[DimExpr]>::to_vec);
let key = ctx.input_shape(1).map(<[DimExpr]>::to_vec);
let value = ctx.input_shape(2).map(<[DimExpr]>::to_vec);
let dtype = ctx.input_dtype(0);
let (Some(query), Some(key), Some(value), Some(dtype)) = (query, key, value, dtype) else {
return Ok(());
};
if query.len() != 3 {
return Err(ShapeInferError::Invalid {
op: "MultiHeadAttention".into(),
detail: format!(
"query must be rank 3 `(batch, sequence, hidden)`; packed-QKV \
layouts are unsupported (got rank {})",
query.len()
),
});
}
if !(key.len() == 3 || key.len() == 4) || key.len() != value.len() {
return Err(ShapeInferError::Invalid {
op: "MultiHeadAttention".into(),
detail: format!(
"key and value must both be rank 3 `(batch, kv_sequence, hidden)` or \
rank 4 `(batch, num_heads, kv_sequence, head_size)` (got key={}, value={})",
key.len(),
value.len()
),
});
}
let num_heads = ctx
.node
.attr("num_heads")
.and_then(Attribute::as_int)
.map(DimExpr::constant)
.ok_or_else(|| ShapeInferError::Invalid {
op: "MultiHeadAttention".into(),
detail: "missing required `num_heads` attribute".into(),
})?;
let batch = query[0].clone();
let query_sequence = query[1].clone();
let query_key_head_size = if key.len() == 4 {
key[3].clone()
} else {
key[2]
.checked_div(&num_heads)
.unwrap_or_else(|| ctx.fresh_dim())
};
let (value_hidden, value_head_size) = if value.len() == 4 {
(num_heads.mul(&value[3]), value[3].clone())
} else {
let value_head_size = value[2]
.checked_div(&num_heads)
.unwrap_or_else(|| ctx.fresh_dim());
(value[2].clone(), value_head_size)
};
let kv_sequence = if key.len() == 3 {
key[1].clone()
} else {
key[2].clone()
};
let total_sequence = match ctx.input_shape(6) {
Some(past_key) if past_key.len() == 4 => past_key[2].add(&kv_sequence),
_ => kv_sequence,
};
ctx.set_output(
0,
dtype,
vec![batch.clone(), query_sequence.clone(), value_hidden],
);
if ctx.num_outputs() > 1 {
ctx.set_output(
1,
dtype,
vec![
batch.clone(),
num_heads.clone(),
total_sequence.clone(),
query_key_head_size,
],
);
}
if ctx.num_outputs() > 2 {
let value_dtype = ctx.input_dtype(2).unwrap_or(dtype);
ctx.set_output(
2,
value_dtype,
vec![
batch.clone(),
num_heads.clone(),
total_sequence.clone(),
value_head_size,
],
);
}
if ctx.num_outputs() > 3 {
ctx.set_output(
3,
dtype,
vec![batch, num_heads, query_sequence, total_sequence],
);
}
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);
reg.register("com.microsoft", "Attention", 1, msft_attention);
reg.register(
"com.microsoft",
"MultiHeadAttention",
1,
multi_head_attention,
);
}