1use std::fmt::Formatter;
2use std::ops::Deref;
3
4use tract_itertools::{izip, multiunzip};
5use tract_linalg::block_quant::PackedBlockQuantFormat;
6
7use super::*;
8use crate::ops::cast::cast;
9use crate::ops::math::add;
10use crate::ops::matmul::ModePicker;
11use crate::ops::matmul::optimized::{
12 AddMatMulGeometry, MapOutputAxisToInput, MatMulOperand, OptMatMul, ProtoFusedSpec,
13};
14use crate::ops::matmul::pack::{OptMatMulPack, OptSimpleMatMulPack};
15use crate::ops::matmul::quant::{
16 combine_scales, compensate_zero_points, requant, wire_ensure_q8_flavour,
17};
18use crate::ops::nn::{Reduce, Reducer};
19
20pub fn merge_consecutive_same_role_axes(model: &mut TypedModel) -> TractResult<()> {
21 Rewriter::default()
22 .with_rule_for("merge-same-role-axes", merge_same_role_axes_rule)
23 .with_rule_for("push-reshape-below-binop", push_reshape_below_binop_rule)
24 .rewrite(&(), model)
25}
26
27fn merge_same_role_axes_rule(
28 _ctx: &(),
29 model: &TypedModel,
30 node: &TypedNode,
31 node_name: &str,
32 op: &EinSum,
33) -> TractResult<Option<TypedModelPatch>> {
34 rule_if!(node.inputs.len() == 2);
36
37 type Role = (bool, bool, bool);
39 let axes: Vec<(char, Role)> = op
40 .axes
41 .iter_all_axes()
42 .map(|a| {
43 (a.repr, (!a.inputs[0].is_empty(), !a.inputs[1].is_empty(), !a.outputs[0].is_empty()))
44 })
45 .collect();
46
47 let a_order: Vec<char> = op.axes.axes(InOut::In(0)).map(|a| a.repr).collect();
49 let b_order: Vec<char> = op.axes.axes(InOut::In(1)).map(|a| a.repr).collect();
50 let c_order: Vec<char> = op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();
51
52 let input_facts = model.node_input_facts(node.id)?;
55 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
56 let is_non_unit = |c: &char| -> bool {
63 let dim = a_order
64 .iter()
65 .position(|x| x == c)
66 .map(|p| &input_shapes[0][p])
67 .or_else(|| b_order.iter().position(|x| x == c).map(|p| &input_shapes[1][p]));
68 dim.is_none_or(|d| d.as_i64() != Some(1))
69 };
70
71 let role_map: std::collections::HashMap<char, Role> = axes.iter().cloned().collect();
74 let mut best_group: Option<Vec<char>> = None;
75
76 let all_orders = [&a_order, &b_order];
78 for (primary_idx, primary_order) in all_orders.iter().enumerate() {
79 let mut i = 0;
80 while i < primary_order.len() {
81 let first = primary_order[i];
82 let first_role = role_map[&first];
83 let mut group = vec![first];
84 let mut j = i + 1;
85 while j < primary_order.len() {
86 let candidate = primary_order[j];
87 if role_map[&candidate] != first_role {
88 break;
89 }
90 let consecutive_in_others = all_orders
92 .iter()
93 .enumerate()
94 .filter(|(idx, _)| *idx != primary_idx)
95 .all(|(_, order)| {
96 let positions: Vec<usize> = group
97 .iter()
98 .chain(std::iter::once(&candidate))
99 .filter_map(|c| order.iter().position(|x| x == c))
100 .collect();
101 if positions.len() <= 1 {
102 return true;
103 }
104 let mut sorted = positions.clone();
105 sorted.sort();
106 sorted == positions
107 && sorted.last().unwrap() - sorted.first().unwrap() == sorted.len() - 1
108 });
109 if !consecutive_in_others {
110 break;
111 }
112 group.push(candidate);
113 j += 1;
114 }
115 if group.len() >= 2 && best_group.as_ref().is_none_or(|bg| group.len() > bg.len()) {
116 best_group = Some(group);
117 }
118 i = j;
119 }
120 }
121
122 if let Some(ref group) = best_group {
123 let dims_match = group.iter().all(|c| {
129 match (a_order.iter().position(|x| x == c), b_order.iter().position(|x| x == c)) {
130 (Some(p0), Some(p1)) => input_shapes[0][p0] == input_shapes[1][p1],
131 _ => true,
132 }
133 });
134 let worth_merging = group.iter().filter(|c| is_non_unit(c)).count() >= 2;
137 if !dims_match || !worth_merging {
138 best_group = None;
139 }
140 }
141
142 if let Some(group) = best_group {
143 let output_shape = super::eval::output_shape(&op.axes, &input_shapes)?;
145
146 let drop_set: Vec<char> = group[1..].to_vec();
147
148 let mut patch = TypedModelPatch::default();
149 let mut wires: TVec<OutletId> = patch.taps(model, &node.inputs)?;
150
151 for (slot, order) in [(0, &a_order), (1, &b_order)] {
153 let positions: Vec<usize> =
154 group.iter().filter_map(|c| order.iter().position(|x| x == c)).collect();
155 if positions.len() < 2 {
156 continue;
157 }
158 let start = positions[0];
159 let from_dims: TVec<TDim> =
160 positions.iter().map(|&p| input_shapes[slot][p].clone()).collect();
161 let merged: TDim = from_dims.iter().product();
162 wires[slot] = patch.wire_node(
163 format!("{node_name}.merge_in{slot}"),
164 AxisOp::Reshape(start, from_dims, tvec![merged]),
165 &[wires[slot]],
166 )?[0];
167 }
168
169 let c_positions: Vec<usize> =
171 group.iter().filter_map(|c| c_order.iter().position(|x| x == c)).collect();
172 let c_needs_reorder = c_positions.len() >= 2 && {
173 let mut sorted = c_positions.clone();
174 sorted.sort();
175 sorted.last().unwrap() - sorted.first().unwrap() != sorted.len() - 1
176 || sorted != c_positions
177 };
178 let mut adjusted_c_order = c_order.clone();
179 if c_needs_reorder {
180 for k in 1..c_positions.len() {
182 let cur_pos = adjusted_c_order.iter().position(|&c| c == group[k]).unwrap();
183 let target_pos =
184 adjusted_c_order.iter().position(|&c| c == group[k - 1]).unwrap() + 1;
185 if cur_pos != target_pos {
186 let removed = adjusted_c_order.remove(cur_pos);
187 let insert_at = if cur_pos < target_pos { target_pos - 1 } else { target_pos };
188 adjusted_c_order.insert(insert_at, removed);
189 }
190 }
191 }
192
193 let in0: String = a_order.iter().collect();
195 let in1: String = b_order.iter().collect();
196 let out: String = adjusted_c_order.iter().collect();
197 let expr = format!("{in0},{in1}->{out}");
198 let mut new_axes: AxesMapping = expr.parse()?;
199 for &drop in &drop_set {
200 new_axes = new_axes.remove_axis(drop)?;
201 }
202 let new_op =
203 EinSum { axes: new_axes, operating_dt: op.operating_dt, q_params: op.q_params };
204 let mut result = patch.wire_node(node_name, new_op, &wires)?;
205
206 let merged_c_positions: Vec<usize> =
208 group.iter().filter_map(|c| adjusted_c_order.iter().position(|x| x == c)).collect();
209 if merged_c_positions.len() >= 2 {
210 let start = merged_c_positions[0];
211 let original_c_positions: Vec<usize> =
213 group.iter().filter_map(|c| c_order.iter().position(|x| x == c)).collect();
214 let original_dims: TVec<TDim> =
215 original_c_positions.iter().map(|&p| output_shape[p].clone()).collect();
216 let merged: TDim = original_dims.iter().product();
217 result[0] = patch.wire_node(
218 format!("{node_name}.unmerge_out"),
219 AxisOp::Reshape(start, tvec![merged], original_dims),
220 &[result[0]],
221 )?[0];
222 }
223
224 if c_needs_reorder {
226 let mut unmerged_adj: Vec<char> = Vec::new();
230 for &c in &adjusted_c_order {
231 if c == group[0] {
232 unmerged_adj.extend(&group);
233 } else if !group.contains(&c) {
234 unmerged_adj.push(c);
235 }
236 }
237 for (target_pos, &c_target) in c_order.iter().enumerate() {
239 let cur_pos = unmerged_adj.iter().position(|&c| c == c_target).unwrap();
240 if cur_pos != target_pos {
241 result[0] = patch.wire_node(
242 format!("{node_name}.restore_out_{target_pos}"),
243 AxisOp::Move(cur_pos, target_pos),
244 &[result[0]],
245 )?[0];
246 let removed = unmerged_adj.remove(cur_pos);
247 unmerged_adj.insert(target_pos, removed);
248 }
249 }
250 }
251
252 patch.shunt_outside(model, node.id.into(), result[0])?;
253 return Ok(Some(patch));
254 }
255
256 let k_role: Role = (true, true, false); let role_of = |c: char| axes.iter().find(|(ch, _)| *ch == c).map(|(_, r)| *r);
260
261 for (slot, order) in [(0usize, &a_order), (1, &b_order)] {
262 for w in order.windows(3) {
265 let (left, mid, right) = (w[0], w[1], w[2]);
266 let left_role = role_of(left);
267 let mid_role = role_of(mid);
268 let right_role = role_of(right);
269 if left_role != right_role || mid_role != Some(k_role) {
270 continue;
271 }
272 if !is_non_unit(&left) || !is_non_unit(&right) {
278 continue;
279 }
280 let other_input_orders: Vec<&Vec<char>> = [(0, &a_order), (1, &b_order)]
283 .iter()
284 .filter(|(s, _)| *s != slot)
285 .map(|(_, o)| *o)
286 .collect();
287 let consecutive_elsewhere = other_input_orders.iter().all(|order| {
288 let lp = order.iter().position(|&c| c == left);
289 let rp = order.iter().position(|&c| c == right);
290 match (lp, rp) {
291 (Some(l), Some(r)) => r == l + 1,
292 _ => true, }
294 });
295 if !consecutive_elsewhere {
296 continue;
297 }
298
299 let mid_pos = order.iter().position(|&c| c == mid).unwrap();
302 let end_pos = order.len() - 1;
303 if mid_pos == end_pos {
304 continue;
305 }
306
307 let move_op = AxisOp::Move(mid_pos, end_pos);
309 let Some(AxisChangeConsequence { substitute_op, .. }) =
310 op.change_axes(model, node, InOut::In(slot), &move_op)?
311 else {
312 continue;
313 };
314 let mut current_op = *substitute_op
315 .unwrap()
316 .downcast::<EinSum>()
317 .map_err(|_| anyhow!("expected EinSum"))?;
318
319 let new_c: Vec<char> = current_op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();
321 let left_c = new_c.iter().position(|&c| c == left);
322 let right_c = new_c.iter().position(|&c| c == right);
323 let need_output_fix = matches!((left_c, right_c), (Some(l), Some(r)) if r != l + 1);
324 if need_output_fix {
325 let r_pos = right_c.unwrap();
326 let l_pos = left_c.unwrap();
327 let target = if r_pos < l_pos { l_pos } else { l_pos + 1 };
328 if let Some(AxisChangeConsequence { substitute_op, .. }) = current_op.change_axes(
329 model,
330 node,
331 InOut::Out(0),
332 &AxisOp::Move(r_pos, target),
333 )? {
334 current_op = *substitute_op
335 .unwrap()
336 .downcast::<EinSum>()
337 .map_err(|_| anyhow!("expected EinSum"))?;
338 }
339 }
340
341 let mut patch = TypedModelPatch::default();
342 let mut wires: TVec<OutletId> = patch.taps(model, &node.inputs)?;
343
344 wires[slot] =
345 patch.wire_node(format!("{node_name}.move_k_in{slot}"), move_op, &[wires[slot]])?
346 [0];
347
348 let final_c: Vec<char> = current_op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();
349 let mut result = patch.wire_node(node_name, current_op, &wires)?;
350
351 if need_output_fix {
353 let r_cur = final_c.iter().position(|&c| c == right).unwrap();
354 let r_orig = c_order.iter().position(|&c| c == right).unwrap();
355 if r_cur != r_orig {
356 result[0] = patch.wire_node(
357 format!("{node_name}.restore_out"),
358 AxisOp::Move(r_cur, r_orig),
359 &[result[0]],
360 )?[0];
361 }
362 }
363
364 patch.shunt_outside(model, node.id.into(), result[0])?;
365 return Ok(Some(patch));
366 }
367 }
368
369 Ok(None)
370}
371
372fn push_reshape_below_binop_rule(
384 _ctx: &(),
385 model: &TypedModel,
386 node: &TypedNode,
387 node_name: &str,
388 op: &AxisOp,
389) -> TractResult<Option<TypedModelPatch>> {
390 let AxisOp::Reshape(at, from_dims, to_dims) = op else { return Ok(None) };
391 rule_if!(node.outputs.len() == 1);
392 rule_if!(node.outputs[0].successors.len() == 1);
393 rule_if!(!model.output_outlets()?.contains(&node.id.into()));
394
395 let inlet = node.outputs[0].successors[0];
396 let succ = model.node(inlet.node);
397 rule_if!(succ.inputs.len() == 2);
398 rule_if_some!(binop = succ.op_as::<crate::ops::binary::TypedBinOp>());
399 rule_if!(binop.0.as_linalg_binop().is_some());
400 let other = succ.inputs[1 - inlet.slot];
401
402 let mut patch = TypedModelPatch::new(format!("push {node} below {succ}"));
403 let act = patch.tap_model(model, node.inputs[0])?;
404
405 let sibling = if let Some(k) = model.outlet_fact(other)?.konst.clone() {
406 let at = *at;
407 let (from_len, to_len) = (from_dims.len(), to_dims.len());
408 let kshape = k.shape();
409 rule_if!(at + to_len <= kshape.len());
410 rule_if!(kshape[at..at + to_len].iter().all(|&d| d == 1));
411 let mut new_shape: TVec<usize> = kshape[..at].iter().copied().collect();
412 new_shape.extend(std::iter::repeat_n(1, from_len));
413 new_shape.extend(kshape[at + to_len..].iter().copied());
414 let k = k.as_ref().clone().into_shape(&new_shape)?;
415 patch.add_const(format!("{}.through_reshape", model.node(other.node).name), k)?
416 } else {
417 let other_node = model.node(other.node);
418 rule_if!(other_node.op_as::<AxisOp>() == Some(op));
419 patch.tap_model(model, other_node.inputs[0])?
420 };
421
422 let operands = if inlet.slot == 0 { [act, sibling] } else { [sibling, act] };
423 let applied = crate::ops::change_axes::wire_with_rank_broadcast(
424 format!("{}.prefused", succ.name),
425 &mut patch,
426 binop.clone(),
427 &operands,
428 )?;
429 let reshaped = patch.wire_node(format!("{node_name}.pushed"), op.clone(), &applied)?;
430 patch.shunt_outside(model, inlet.node.into(), reshaped[0])?;
431 Ok(Some(patch))
432}
433
434pub fn detect_all(model: &mut TypedModel) -> TractResult<()> {
435 Rewriter::default().with_rule_for("detect-matmul-einsum", detect_rule).rewrite(&(), model)
436}
437
438pub fn flatten_all(model: &mut TypedModel) -> TractResult<()> {
439 Rewriter::default().with_rule_for("flatten-matmul-einsum", flatten_rule).rewrite(&(), model)
440}
441
442#[derive(Clone, Hash, PartialEq, Eq)]
443pub struct EinSumMatMul {
444 pub op: EinSum,
445 pub m_axis: char,
446 pub k_axis: char,
447 pub n_axis: char,
448 pub m: TDim,
449 pub k: TDim,
450 pub n: TDim,
451}
452
453impl EinSumMatMul {
454 pub fn m_axis(&self) -> &Axis {
455 self.op.axes.axis(self.m_axis).unwrap()
456 }
457 pub fn k_axis(&self) -> &Axis {
458 self.op.axes.axis(self.k_axis).unwrap()
459 }
460 pub fn n_axis(&self) -> &Axis {
461 self.op.axes.axis(self.n_axis).unwrap()
462 }
463 pub fn a_m(&self) -> usize {
464 self.m_axis().inputs[0][0]
465 }
466 pub fn a_k(&self) -> usize {
467 self.k_axis().inputs[0][0]
468 }
469 pub fn b_k(&self) -> usize {
470 self.k_axis().inputs[1][0]
471 }
472 pub fn b_n(&self) -> usize {
473 self.n_axis().inputs[1][0]
474 }
475 pub fn c_m(&self) -> Option<usize> {
476 self.m_axis().outputs[0].first().cloned()
477 }
478 pub fn c_n(&self) -> Option<usize> {
479 self.n_axis().outputs[0].first().cloned()
480 }
481
482 fn new(
483 op: EinSum,
484 m_axis: char,
485 k_axis: char,
486 n_axis: char,
487 m: TDim,
488 k: TDim,
489 n: TDim,
490 ) -> Self {
491 Self { op, m_axis, k_axis, n_axis, m, k, n }
492 }
493}
494
495impl Debug for EinSumMatMul {
496 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
497 write!(
498 f,
499 "EinsumMatMul: {} {:?} m: {}={}; k: {}={}; n: {}={}",
500 self.op.axes,
501 self.op.operating_dt,
502 self.m_axis,
503 self.m,
504 self.k_axis,
505 self.k,
506 self.n_axis,
507 self.n
508 )
509 }
510}
511
512impl Deref for EinSumMatMul {
513 type Target = EinSum;
514 fn deref(&self) -> &Self::Target {
515 &self.op
516 }
517}
518
519impl Op for EinSumMatMul {
520 fn name(&self) -> StaticName {
521 "EinSumMatMul".into()
522 }
523
524 op_as_typed_op!();
525}
526
527impl EvalOp for EinSumMatMul {
528 op_out_of_plan!();
529 fn eval(&self, ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
530 self.op.eval(ctx, inputs)
531 }
532}
533
534impl TypedOp for EinSumMatMul {
535 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
536 self.op.output_facts(inputs)
537 }
538
539 fn codegen(
540 &self,
541 model: &TypedModel,
542 node: &TypedNode,
543 ) -> TractResult<Option<TypedModelPatch>> {
544 if node.inputs.len() == 9 {
546 ensure!(self.op.q_params.is_some());
547 return dequant(model, node, self).map(Some);
548 }
549 ensure!(node.inputs.len() == 2);
550 let (a, b) = model.node_input_facts(node.id)?.into_iter().collect_tuple().unwrap();
551 let must_transpose = if let Some(of) = a.exotic_fact() {
553 ensure!(of.is::<BlockQuantFact>());
554 false
555 } else if let Some(of) = b.exotic_fact() {
556 ensure!(of.is::<BlockQuantFact>());
557 true
558 } else if self.m == self.n {
559 false
560 } else {
561 match (self.m.as_i64(), self.n.as_i64()) {
562 (Some(m), Some(n)) => m < n,
563 (None, Some(n)) => n >= 8,
564 (Some(_), _) => false,
565 _ => (self.n.clone() - &self.m).prove_positive_or_zero(),
566 }
567 };
568 if must_transpose {
569 let mut op = self.clone();
570 op.op.axes.iter_all_axes_mut().for_each(|axis| axis.inputs.swap(0, 1));
571 std::mem::swap(&mut op.m_axis, &mut op.n_axis);
572 std::mem::swap(&mut op.m, &mut op.n);
573 return TypedModelPatch::replace_single_op(
574 model,
575 node,
576 &[node.inputs[1], node.inputs[0]],
577 op,
578 )
579 .map(|p| Some(p.with_context("transposing")));
580 }
581 if self.c_m().is_some() || self.c_n().is_some() {
583 return optimized_mat_mul(model, node, self)
584 .map(|opt| opt.map(|p| p.with_context("optimizing")));
585 }
586 Ok(None)
587 }
588
589 as_op!();
590}
591
592pub(crate) fn detect_rule(
593 _ctx: &(),
594 model: &TypedModel,
595 node: &TypedNode,
596 _name: &str,
597 op: &EinSum,
598) -> TractResult<Option<TypedModelPatch>> {
599 rule_if!(node.inputs.len() == (2 + op.q_params.is_some() as usize * 7));
600 let input_facts = model.node_input_facts(node.id)?;
601 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
602 let output_shape = super::eval::output_shape(&op.axes, &input_shapes)?;
603 let k_axes: TVec<&Axis> = op
604 .axes
605 .iter_all_axes()
606 .filter(|a| a.inputs[0].len() == 1 && a.inputs[1].len() == 1 && a.outputs[0].is_empty())
608 .collect();
609
610 let non_trivial_k_axis = k_axes
611 .iter()
612 .filter(|a| {
613 !input_shapes[0][a.inputs[0][0]].is_one() || !input_shapes[1][a.inputs[1][0]].is_one()
614 })
615 .copied()
616 .collect::<TVec<_>>();
617
618 let k_axis = if non_trivial_k_axis.len() > 1 {
619 return regroup_k_axes(op, model, node, non_trivial_k_axis);
620 } else {
621 non_trivial_k_axis.first().or_else(|| k_axes.first()).copied()
622 };
623 let Some(k_axis) = k_axis else { return inject_k_axis(op, model, node).map(Some) };
624
625 let mut possible_m_axes: Vec<_> = op
626 .axes
627 .iter_all_axes()
628 .filter(|a| {
629 a.inputs[0].len() == 1
630 && (a.inputs[1].is_empty() || input_shapes[1][a.inputs[1][0]].is_one())
631 && (a.outputs[0].len() == 1
632 || (input_shapes[0][a.inputs[0][0]].is_one() && a.inputs[1].is_empty()))
633 })
634 .collect();
635
636 if possible_m_axes.iter().any(|a| !a.outputs[0].is_empty()) {
638 possible_m_axes.retain(|a| !a.outputs[0].is_empty());
639 }
640
641 let m_axis = possible_m_axes
642 .into_iter()
643 .max_by_key(|a| input_shapes[0][a.inputs[0][0]].as_i64().unwrap_or(i64::MAX));
644
645 let Some(m_axis) = m_axis else {
646 return inject_m_or_n_axis(op, model, node, false).map(Some);
647 };
648
649 let n_axis = op
650 .axes
651 .iter_all_axes()
652 .filter(|a| {
653 (a.inputs[0].is_empty() || input_shapes[0][a.inputs[0][0]].is_one())
654 && a.inputs[1].len() == 1
655 && a.outputs[0].len() == 1
656 && *a != m_axis
657 })
658 .max_by_key(|a| input_shapes[1][a.inputs[1][0]].as_i64().unwrap_or(i64::MAX));
659 let Some(n_axis) = n_axis else {
660 return inject_m_or_n_axis(op, model, node, true).map(Some);
661 };
662 for axis in op.axes.iter_all_axes() {
663 let one = TDim::one();
664 let in_left =
665 axis.inputs[0].first().map(|pos| &input_shapes[0][*pos]).unwrap_or(&one) != &one;
666 let in_right =
667 axis.inputs[1].first().map(|pos| &input_shapes[1][*pos]).unwrap_or(&one) != &one;
668 let in_out = axis.outputs[0].first().map(|pos| &output_shape[*pos]).unwrap_or(&one) != &one;
669 if (in_left ^ in_right) && !in_out {
670 return Ok(None);
671 }
676 }
677 let m = input_shapes[0][m_axis.inputs[0][0]].clone();
678 let k = input_shapes[0][k_axis.inputs[0][0]].clone();
679 let n = input_shapes[1][n_axis.inputs[1][0]].clone();
680 TypedModelPatch::replace_single_op(
681 model,
682 node,
683 &node.inputs,
684 EinSumMatMul::new(op.clone(), m_axis.repr, k_axis.repr, n_axis.repr, m, k, n),
685 )
686 .map(Some)
687}
688
689pub(super) fn inject_k_axis(
690 op: &EinSum,
691 model: &TypedModel,
692 node: &TypedNode,
693) -> TractResult<TypedModelPatch> {
694 let mut new_axes = op.axes.clone();
695 let name = &node.name;
696 let mut patch = TypedModelPatch::new("inject k axis");
697 let mut wire = patch.taps(model, &node.inputs)?;
698 let repr = new_axes.available_label();
699 new_axes = new_axes.with_extra_axis(repr, InOut::In(0), 0)?.with_extra_axis_occurency(
700 repr,
701 InOut::In(1),
702 0,
703 )?;
704 wire[0] = patch.wire_node(format!("{name}.add_k.0"), AxisOp::Add(0), &[wire[0]])?[0];
705 wire[1] = patch.wire_node(format!("{name}.add_k.1"), AxisOp::Add(0), &[wire[1]])?[0];
706 wire = patch.wire_node(&node.name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;
707 patch.shunt_outside(model, node.id.into(), wire[0])?;
708 Ok(patch)
709}
710
711pub(super) fn regroup_k_axes(
712 op: &EinSum,
713 model: &TypedModel,
714 node: &TypedNode,
715 mut k_axes: TVec<&Axis>,
716) -> TractResult<Option<TypedModelPatch>> {
717 let input_facts = model.node_input_facts(node.id)?;
718 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
719 let contig_in_a = k_axes
720 .iter()
721 .map(|axis| axis.inputs[0][0])
722 .sorted()
723 .tuple_windows()
724 .all(|(a, b)| a + 1 == b);
725 if contig_in_a {
726 k_axes.sort_by_key(|ax| ax.inputs[0][0]);
727 } else {
728 k_axes.sort_by_key(|ax| ax.inputs[1][0]);
729 }
730 let k_dims: TVec<_> =
731 k_axes.iter().map(|ax| input_shapes[0][ax.inputs[0][0]].clone()).collect();
732 let k: TDim = k_dims.iter().product();
733 let mut patch = TypedModelPatch::default();
734 let mut wires = patch.taps(model, &node.inputs)?;
735 let mut exprs: Vec<String> =
736 (0..2).map(|slot| op.axes.axes(InOut::In(slot)).map(|ax| ax.repr).join("")).collect();
737 for slot in 0..2 {
738 if k_axes.iter().map(|ax| ax.inputs[slot][0]).tuple_windows().any(|(a, b)| a + 1 != b) {
739 let after = op
740 .axes
741 .axes(InOut::In(slot))
742 .filter(|ax| !k_axes.contains(ax))
743 .chain(k_axes.iter().copied())
744 .map(|ax| ax.repr)
745 .join("");
746 let transpose =
747 AxesMapping::from_strs(&[&exprs[slot]], &[&after])?.translate_to_axis_ops()?;
748 for (ix, op) in transpose.into_iter().enumerate() {
749 wires[slot] = patch.wire_node(
750 format!("{}.transpose_input_{}.{}", node.name, slot, ix),
751 op,
752 &[wires[slot]],
753 )?[0];
754 }
755 exprs[slot] = after;
756 }
757 let pos = exprs[slot].chars().position(|c| k_axes[0].repr == c).unwrap();
758 wires[slot] = patch.wire_node(
759 format!("{}.fold_k_in_input_{}", node.name, slot),
760 AxisOp::Reshape(pos, k_dims.clone(), tvec!(k.clone())),
761 &[wires[slot]],
762 )?[0];
763 exprs[slot] =
764 exprs[slot].chars().filter(|c| !k_axes.iter().any(|k| k.repr == *c)).collect();
765 exprs[slot].insert(pos, k_axes[0].repr);
766 }
767 let old = op.axes.to_string();
768 let (iexpr, oexpr) = old.split_once("->").unwrap();
769 let mut expr: String = exprs.iter().join(",");
770 if node.inputs.len() > 2 {
771 expr = expr + "," + &iexpr.split(",").skip(2).join(",");
772 }
773 expr = expr + "->" + oexpr;
774 let wire = patch.wire_node(
775 &node.name,
776 EinSum { axes: expr.parse().unwrap(), ..op.clone() },
777 &wires,
778 )?[0];
779 patch.shunt_outside(model, node.id.into(), wire)?;
780 Ok(Some(patch))
781}
782
783pub(super) fn inject_m_or_n_axis(
784 op: &EinSum,
785 model: &TypedModel,
786 node: &TypedNode,
787 is_n: bool,
788) -> TractResult<TypedModelPatch> {
789 let input_to_fix = is_n as usize;
790 let label = if is_n { "n" } else { "m" };
791 let name = &node.name;
792 let mut patch = TypedModelPatch::new("Injecting m or n axis");
793 let mut wire = patch.taps(model, &node.inputs)?;
794 let repr = op.axes.available_label();
795 let new_axes = op
796 .axes
797 .clone()
798 .with_extra_axis(repr, InOut::In(input_to_fix), 0)?
799 .with_extra_axis_occurency(repr, InOut::Out(0), 0)?;
800 wire[input_to_fix] =
801 patch.wire_node(format!("{name}.add_{label}"), AxisOp::Add(0), &[wire[input_to_fix]])?[0];
802 wire = patch.wire_node(name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;
803 wire = patch.wire_node(&node.name, AxisOp::Rm(0), &wire)?;
804 patch.shunt_outside(model, node.id.into(), wire[0])?;
805 Ok(patch)
806}
807
808fn wire_axes_fix(
809 patch: &mut TypedModelPatch,
810 name: &str,
811 var: &str,
812 mapping: &AxesMapping,
813 mut outlet: TVec<OutletId>,
814) -> TractResult<TVec<OutletId>> {
815 for (ix, axis_op) in mapping.translate_to_axis_ops()?.into_iter().enumerate() {
816 outlet = patch.wire_node(format!("{name}.fix_{var}.{ix})"), axis_op, &outlet)?;
817 }
818 Ok(outlet)
819}
820
821fn dequant(
822 model: &TypedModel,
823 node: &TypedNode,
824 op: &EinSumMatMul,
825) -> TractResult<TypedModelPatch> {
826 let name = &node.name;
827 let mut patch = TypedModelPatch::new("Dequantizing einsum");
828
829 let k_axis = op.k_axis();
830
831 let mut taps = patch.taps(model, &node.inputs)?;
832 for ab in [0, 1] {
833 let scale_input = 4 + ab * 2;
834 if !patch.outlet_fact(taps[scale_input])?.shape.volume().is_one() {
835 let q_axis_in_output = op.axes.axis((InOut::In(scale_input), 0))?.outputs[0][0];
836 let output_rank = node.outputs[0].fact.rank();
837 for i in 1..(output_rank - q_axis_in_output) {
838 taps[scale_input] = patch.wire_node(
839 format!("{name}.scale_input{ab}_axis_fix_{i}"),
840 AxisOp::Add(i),
841 &[taps[scale_input]],
842 )?[0];
843 }
844 }
845 }
846
847 let [mut a, mut b, bias, mut a0, a_scale, mut b0, b_scale, c0, c_scale] = *taps else {
848 bail!("Expect exactly 9 inputs")
849 };
850
851 wire_ensure_q8_flavour(&mut patch, &node.name, &mut a, "a", &mut a0, i8::datum_type())?;
852 wire_ensure_q8_flavour(&mut patch, &node.name, &mut b, "b", &mut b0, i8::datum_type())?;
853
854 let mut output = patch.wire_node(
855 &node.name,
856 EinSum {
857 q_params: None,
858 axes: op.axes.extract_sub_mapping(&[0, 1], &[0])?,
859 operating_dt: op.operating_dt,
860 },
861 &[a, b],
862 )?;
863
864 let a_i32 = patch.wire_node(format!("{name}.a_as_i32"), cast(i32::datum_type()), &[a])?[0];
865 let b_i32 = patch.wire_node(format!("{name}.b_as_i32"), cast(i32::datum_type()), &[b])?[0];
866 let sum_a = patch.wire_node(
867 format!("{name}.sum_a"),
868 Reduce::new(tvec!(k_axis.inputs[0][0]), Reducer::Sum),
869 &[a_i32],
870 )?;
871 let sum_b = patch.wire_node(
872 format!("{name}.sum_b"),
873 Reduce::new(tvec!(k_axis.inputs[1][0]), Reducer::Sum),
874 &[b_i32],
875 )?;
876
877 let sum_a =
878 wire_axes_fix(&mut patch, name, "sum_a", &op.axes.extract_sub_mapping(&[0], &[0])?, sum_a)?;
879 let sum_b =
880 wire_axes_fix(&mut patch, name, "sum_b", &op.axes.extract_sub_mapping(&[1], &[0])?, sum_b)?;
881 let bias = tvec!(bias);
882 let bias =
883 wire_axes_fix(&mut patch, name, "bias", &op.axes.extract_sub_mapping(&[2], &[0])?, bias)?;
884
885 let abc_scale = combine_scales(&mut patch, name, a_scale, b_scale, c_scale)?;
886
887 output = patch.wire_node(format!("{name}.add_bias"), add(), &[output[0], bias[0]])?;
888
889 let k = model.outlet_fact(node.inputs[0])?.shape[k_axis.inputs[0][0]].clone();
890 let output = compensate_zero_points(&mut patch, name, output[0], k, a0, b0, sum_a[0], sum_b[0])
891 .context("Zero point compensation")?;
892 let output = requant(&mut patch, name, output, op.q_params.unwrap(), abc_scale, c0)?;
893 patch.shunt_outside(model, node.id.into(), output)?;
894 Ok(patch)
895}
896
897fn flatten_rule(
898 _ctx: &(),
899 model: &TypedModel,
900 node: &TypedNode,
901 _name: &str,
902 op: &EinSumMatMul,
903) -> TractResult<Option<TypedModelPatch>> {
904 TypedModelPatch::replace_single_op(model, node, &node.inputs, op.op.clone()).map(Some)
905}
906
907fn optimized_mat_mul(
908 model: &TypedModel,
909 node: &TypedNode,
910 op: &EinSumMatMul,
911) -> TractResult<Option<TypedModelPatch>> {
912 let (mode_picker, left_pack, impls) = kernel_selection::strategize(model, node, op)?;
913 let input_facts = model.node_input_facts(node.id)?;
914 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
915 let prefix = &node.name;
916
917 let mut patch = TypedModelPatch::new("Einsum to OptMatMul");
918 let taps = patch.taps(model, &node.inputs)?;
919 let name = &node.name;
920
921 let pack_a: Box<dyn TypedOp> = if input_facts[0].konst.is_some() {
922 if let Some(packed_format) = left_pack.downcast_ref::<PackedBlockQuantFormat>().cloned() {
923 Box::new(OptSimpleMatMulPack {
924 packed_format,
925 k: input_shapes[0][op.a_k()].to_usize().unwrap(),
926 m: input_shapes[0][op.a_m()].to_usize().unwrap(),
927 })
928 } else {
929 Box::new(OptMatMulPack {
932 packers: vec![left_pack],
933 mode_picker: ModePicker::Single,
934 k_axis: op.a_k(),
935 mn_axis: op.a_m(),
936 })
937 }
938 } else {
939 Box::new(OptMatMulPack {
940 packers: impls
941 .iter()
942 .map(|(mmm, p, pe)| {
943 pe.as_ref()
944 .map(|pe| pe.from.clone())
945 .unwrap_or_else(|| mmm.packings()[*p].0.clone())
946 })
947 .collect(),
948 mode_picker: mode_picker.clone(),
949 k_axis: op.a_k(),
950 mn_axis: op.a_m(),
951 })
952 };
953 let pa = patch.wire_node(format!("{prefix}.pack_a"), pack_a, &[taps[0]])?[0];
954
955 let pb = patch.wire_node(
956 format!("{prefix}.pack_b"),
957 OptMatMulPack {
958 k_axis: op.b_k(),
959 mn_axis: op.b_n(),
960 packers: impls.iter().map(|(mmm, p, _)| mmm.packings()[*p].1.clone()).collect(),
961 mode_picker: mode_picker.clone(),
962 },
963 &[taps[1]],
964 )?[0];
965
966 let mut c_to_a_axis_mapping = tvec!();
967 let mut c_to_b_axis_mapping = tvec!();
968 for axis in op
969 .op
970 .axes
971 .iter_all_axes()
972 .filter(|&axis| ![op.m_axis, op.k_axis, op.n_axis].contains(&axis.repr))
973 {
974 if let (&[c], &[a]) = (&*axis.outputs[0], &*axis.inputs[0])
975 && input_shapes[0][a] != 1.to_dim()
976 {
977 let a = a - (a > op.a_m()) as usize - (a > op.a_k()) as usize;
978 c_to_a_axis_mapping.push((c, a));
979 }
980 if let (&[c], &[b]) = (&*axis.outputs[0], &*axis.inputs[1])
981 && input_shapes[1][b] != 1.to_dim()
982 {
983 let b = b - (b > op.b_n()) as usize - (b > op.b_k()) as usize;
984 c_to_b_axis_mapping.push((c, b));
985 }
986 }
987
988 let c_fact = op.output_facts(&input_facts)?.remove(0);
989 let geo = AddMatMulGeometry {
990 k: op.k.clone(),
991 c_to_a_axis_mapping: MapOutputAxisToInput(c_to_a_axis_mapping),
992 c_to_b_axis_mapping: MapOutputAxisToInput(c_to_b_axis_mapping),
993 };
994 let (mmms, packings, extractor): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(impls);
995 let outputs = mmms.iter().map(|mmm| unsafe { mmm.c_view(op.c_m(), op.c_n()) }).collect();
996 let trivial_packing = mmms.len() == 1
997 && packings[0] == 0
998 && extractor[0].is_none()
999 && input_facts[0].exotic_fact.is_none();
1000 let opt = OptMatMul::new(
1001 mmms,
1002 mode_picker,
1003 c_fact,
1004 op.c_m(),
1005 op.c_n(),
1006 vec![
1007 ProtoFusedSpec::AddMatMul {
1008 geo,
1009 a: MatMulOperand::Input(0),
1010 b: MatMulOperand::Input(1),
1011 packings: izip!(packings, extractor).collect_vec(),
1012 },
1013 ProtoFusedSpec::Store(outputs),
1014 ],
1015 trivial_packing,
1016 )
1017 .context("Creating OptMatMul")?;
1018 let output = patch.wire_node(name, opt, &[pa, pb])?[0];
1019 patch.shunt_outside(model, node.id.into(), output)?;
1020 Ok(Some(patch))
1021}