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 fn is_stateless(&self) -> bool {
529 true
530 }
531 fn eval_with_session(
532 &self,
533 node_id: usize,
534 session: &TurnState,
535 inputs: TVec<TValue>,
536 ) -> TractResult<TVec<TValue>> {
537 self.op.eval_with_session(node_id, session, inputs)
538 }
539}
540
541impl TypedOp for EinSumMatMul {
542 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
543 self.op.output_facts(inputs)
544 }
545
546 fn codegen(
547 &self,
548 model: &TypedModel,
549 node: &TypedNode,
550 ) -> TractResult<Option<TypedModelPatch>> {
551 if node.inputs.len() == 9 {
553 ensure!(self.op.q_params.is_some());
554 return dequant(model, node, self).map(Some);
555 }
556 ensure!(node.inputs.len() == 2);
557 let (a, b) = model.node_input_facts(node.id)?.into_iter().collect_tuple().unwrap();
558 let must_transpose = if let Some(of) = a.exotic_fact() {
560 ensure!(of.is::<BlockQuantFact>());
561 false
562 } else if let Some(of) = b.exotic_fact() {
563 ensure!(of.is::<BlockQuantFact>());
564 true
565 } else if self.m == self.n {
566 false
567 } else {
568 match (self.m.as_i64(), self.n.as_i64()) {
569 (Some(m), Some(n)) => m < n,
570 (None, Some(n)) => n >= 8,
571 (Some(_), _) => false,
572 _ => (self.n.clone() - &self.m).prove_positive_or_zero(),
573 }
574 };
575 if must_transpose {
576 let mut op = self.clone();
577 op.op.axes.iter_all_axes_mut().for_each(|axis| axis.inputs.swap(0, 1));
578 std::mem::swap(&mut op.m_axis, &mut op.n_axis);
579 std::mem::swap(&mut op.m, &mut op.n);
580 return TypedModelPatch::replace_single_op(
581 model,
582 node,
583 &[node.inputs[1], node.inputs[0]],
584 op,
585 )
586 .map(|p| Some(p.with_context("transposing")));
587 }
588 if self.c_m().is_some() || self.c_n().is_some() {
590 return optimized_mat_mul(model, node, self)
591 .map(|opt| opt.map(|p| p.with_context("optimizing")));
592 }
593 Ok(None)
594 }
595
596 as_op!();
597}
598
599pub(crate) fn detect_rule(
600 _ctx: &(),
601 model: &TypedModel,
602 node: &TypedNode,
603 _name: &str,
604 op: &EinSum,
605) -> TractResult<Option<TypedModelPatch>> {
606 rule_if!(node.inputs.len() == (2 + op.q_params.is_some() as usize * 7));
607 let input_facts = model.node_input_facts(node.id)?;
608 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
609 let output_shape = super::eval::output_shape(&op.axes, &input_shapes)?;
610 let k_axes: TVec<&Axis> = op
611 .axes
612 .iter_all_axes()
613 .filter(|a| a.inputs[0].len() == 1 && a.inputs[1].len() == 1 && a.outputs[0].is_empty())
615 .collect();
616
617 let non_trivial_k_axis = k_axes
618 .iter()
619 .filter(|a| {
620 !input_shapes[0][a.inputs[0][0]].is_one() || !input_shapes[1][a.inputs[1][0]].is_one()
621 })
622 .copied()
623 .collect::<TVec<_>>();
624
625 let k_axis = if non_trivial_k_axis.len() > 1 {
626 return regroup_k_axes(op, model, node, non_trivial_k_axis);
627 } else {
628 non_trivial_k_axis.first().or_else(|| k_axes.first()).copied()
629 };
630 let Some(k_axis) = k_axis else { return inject_k_axis(op, model, node).map(Some) };
631
632 let mut possible_m_axes: Vec<_> = op
633 .axes
634 .iter_all_axes()
635 .filter(|a| {
636 a.inputs[0].len() == 1
637 && (a.inputs[1].is_empty() || input_shapes[1][a.inputs[1][0]].is_one())
638 && (a.outputs[0].len() == 1
639 || (input_shapes[0][a.inputs[0][0]].is_one() && a.inputs[1].is_empty()))
640 })
641 .collect();
642
643 if possible_m_axes.iter().any(|a| !a.outputs[0].is_empty()) {
645 possible_m_axes.retain(|a| !a.outputs[0].is_empty());
646 }
647
648 let m_axis = possible_m_axes
649 .into_iter()
650 .max_by_key(|a| input_shapes[0][a.inputs[0][0]].as_i64().unwrap_or(i64::MAX));
651
652 let Some(m_axis) = m_axis else {
653 return inject_m_or_n_axis(op, model, node, false).map(Some);
654 };
655
656 let n_axis = op
657 .axes
658 .iter_all_axes()
659 .filter(|a| {
660 (a.inputs[0].is_empty() || input_shapes[0][a.inputs[0][0]].is_one())
661 && a.inputs[1].len() == 1
662 && a.outputs[0].len() == 1
663 && *a != m_axis
664 })
665 .max_by_key(|a| input_shapes[1][a.inputs[1][0]].as_i64().unwrap_or(i64::MAX));
666 let Some(n_axis) = n_axis else {
667 return inject_m_or_n_axis(op, model, node, true).map(Some);
668 };
669 for axis in op.axes.iter_all_axes() {
670 let one = TDim::one();
671 let in_left =
672 axis.inputs[0].first().map(|pos| &input_shapes[0][*pos]).unwrap_or(&one) != &one;
673 let in_right =
674 axis.inputs[1].first().map(|pos| &input_shapes[1][*pos]).unwrap_or(&one) != &one;
675 let in_out = axis.outputs[0].first().map(|pos| &output_shape[*pos]).unwrap_or(&one) != &one;
676 if (in_left ^ in_right) && !in_out {
677 return Ok(None);
678 }
683 }
684 let m = input_shapes[0][m_axis.inputs[0][0]].clone();
685 let k = input_shapes[0][k_axis.inputs[0][0]].clone();
686 let n = input_shapes[1][n_axis.inputs[1][0]].clone();
687 TypedModelPatch::replace_single_op(
688 model,
689 node,
690 &node.inputs,
691 EinSumMatMul::new(op.clone(), m_axis.repr, k_axis.repr, n_axis.repr, m, k, n),
692 )
693 .map(Some)
694}
695
696pub(super) fn inject_k_axis(
697 op: &EinSum,
698 model: &TypedModel,
699 node: &TypedNode,
700) -> TractResult<TypedModelPatch> {
701 let mut new_axes = op.axes.clone();
702 let name = &node.name;
703 let mut patch = TypedModelPatch::new("inject k axis");
704 let mut wire = patch.taps(model, &node.inputs)?;
705 let repr = new_axes.available_label();
706 new_axes = new_axes.with_extra_axis(repr, InOut::In(0), 0)?.with_extra_axis_occurency(
707 repr,
708 InOut::In(1),
709 0,
710 )?;
711 wire[0] = patch.wire_node(format!("{name}.add_k.0"), AxisOp::Add(0), &[wire[0]])?[0];
712 wire[1] = patch.wire_node(format!("{name}.add_k.1"), AxisOp::Add(0), &[wire[1]])?[0];
713 wire = patch.wire_node(&node.name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;
714 patch.shunt_outside(model, node.id.into(), wire[0])?;
715 Ok(patch)
716}
717
718pub(super) fn regroup_k_axes(
719 op: &EinSum,
720 model: &TypedModel,
721 node: &TypedNode,
722 mut k_axes: TVec<&Axis>,
723) -> TractResult<Option<TypedModelPatch>> {
724 let input_facts = model.node_input_facts(node.id)?;
725 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
726 let contig_in_a = k_axes
727 .iter()
728 .map(|axis| axis.inputs[0][0])
729 .sorted()
730 .tuple_windows()
731 .all(|(a, b)| a + 1 == b);
732 if contig_in_a {
733 k_axes.sort_by_key(|ax| ax.inputs[0][0]);
734 } else {
735 k_axes.sort_by_key(|ax| ax.inputs[1][0]);
736 }
737 let k_dims: TVec<_> =
738 k_axes.iter().map(|ax| input_shapes[0][ax.inputs[0][0]].clone()).collect();
739 let k: TDim = k_dims.iter().product();
740 let mut patch = TypedModelPatch::default();
741 let mut wires = patch.taps(model, &node.inputs)?;
742 let mut exprs: Vec<String> =
743 (0..2).map(|slot| op.axes.axes(InOut::In(slot)).map(|ax| ax.repr).join("")).collect();
744 for slot in 0..2 {
745 if k_axes.iter().map(|ax| ax.inputs[slot][0]).tuple_windows().any(|(a, b)| a + 1 != b) {
746 let after = op
747 .axes
748 .axes(InOut::In(slot))
749 .filter(|ax| !k_axes.contains(ax))
750 .chain(k_axes.iter().copied())
751 .map(|ax| ax.repr)
752 .join("");
753 let transpose =
754 AxesMapping::from_strs(&[&exprs[slot]], &[&after])?.translate_to_axis_ops()?;
755 for (ix, op) in transpose.into_iter().enumerate() {
756 wires[slot] = patch.wire_node(
757 format!("{}.transpose_input_{}.{}", node.name, slot, ix),
758 op,
759 &[wires[slot]],
760 )?[0];
761 }
762 exprs[slot] = after;
763 }
764 let pos = exprs[slot].chars().position(|c| k_axes[0].repr == c).unwrap();
765 wires[slot] = patch.wire_node(
766 format!("{}.fold_k_in_input_{}", node.name, slot),
767 AxisOp::Reshape(pos, k_dims.clone(), tvec!(k.clone())),
768 &[wires[slot]],
769 )?[0];
770 exprs[slot] =
771 exprs[slot].chars().filter(|c| !k_axes.iter().any(|k| k.repr == *c)).collect();
772 exprs[slot].insert(pos, k_axes[0].repr);
773 }
774 let old = op.axes.to_string();
775 let (iexpr, oexpr) = old.split_once("->").unwrap();
776 let mut expr: String = exprs.iter().join(",");
777 if node.inputs.len() > 2 {
778 expr = expr + "," + &iexpr.split(",").skip(2).join(",");
779 }
780 expr = expr + "->" + oexpr;
781 let wire = patch.wire_node(
782 &node.name,
783 EinSum { axes: expr.parse().unwrap(), ..op.clone() },
784 &wires,
785 )?[0];
786 patch.shunt_outside(model, node.id.into(), wire)?;
787 Ok(Some(patch))
788}
789
790pub(super) fn inject_m_or_n_axis(
791 op: &EinSum,
792 model: &TypedModel,
793 node: &TypedNode,
794 is_n: bool,
795) -> TractResult<TypedModelPatch> {
796 let input_to_fix = is_n as usize;
797 let label = if is_n { "n" } else { "m" };
798 let name = &node.name;
799 let mut patch = TypedModelPatch::new("Injecting m or n axis");
800 let mut wire = patch.taps(model, &node.inputs)?;
801 let repr = op.axes.available_label();
802 let new_axes = op
803 .axes
804 .clone()
805 .with_extra_axis(repr, InOut::In(input_to_fix), 0)?
806 .with_extra_axis_occurency(repr, InOut::Out(0), 0)?;
807 wire[input_to_fix] =
808 patch.wire_node(format!("{name}.add_{label}"), AxisOp::Add(0), &[wire[input_to_fix]])?[0];
809 wire = patch.wire_node(name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;
810 wire = patch.wire_node(&node.name, AxisOp::Rm(0), &wire)?;
811 patch.shunt_outside(model, node.id.into(), wire[0])?;
812 Ok(patch)
813}
814
815fn wire_axes_fix(
816 patch: &mut TypedModelPatch,
817 name: &str,
818 var: &str,
819 mapping: &AxesMapping,
820 mut outlet: TVec<OutletId>,
821) -> TractResult<TVec<OutletId>> {
822 for (ix, axis_op) in mapping.translate_to_axis_ops()?.into_iter().enumerate() {
823 outlet = patch.wire_node(format!("{name}.fix_{var}.{ix})"), axis_op, &outlet)?;
824 }
825 Ok(outlet)
826}
827
828fn dequant(
829 model: &TypedModel,
830 node: &TypedNode,
831 op: &EinSumMatMul,
832) -> TractResult<TypedModelPatch> {
833 let name = &node.name;
834 let mut patch = TypedModelPatch::new("Dequantizing einsum");
835
836 let k_axis = op.k_axis();
837
838 let mut taps = patch.taps(model, &node.inputs)?;
839 for ab in [0, 1] {
840 let scale_input = 4 + ab * 2;
841 if !patch.outlet_fact(taps[scale_input])?.shape.volume().is_one() {
842 let q_axis_in_output = op.axes.axis((InOut::In(scale_input), 0))?.outputs[0][0];
843 let output_rank = node.outputs[0].fact.rank();
844 for i in 1..(output_rank - q_axis_in_output) {
845 taps[scale_input] = patch.wire_node(
846 format!("{name}.scale_input{ab}_axis_fix_{i}"),
847 AxisOp::Add(i),
848 &[taps[scale_input]],
849 )?[0];
850 }
851 }
852 }
853
854 let [mut a, mut b, bias, mut a0, a_scale, mut b0, b_scale, c0, c_scale] = *taps else {
855 bail!("Expect exactly 9 inputs")
856 };
857
858 wire_ensure_q8_flavour(&mut patch, &node.name, &mut a, "a", &mut a0, i8::datum_type())?;
859 wire_ensure_q8_flavour(&mut patch, &node.name, &mut b, "b", &mut b0, i8::datum_type())?;
860
861 let mut output = patch.wire_node(
862 &node.name,
863 EinSum {
864 q_params: None,
865 axes: op.axes.extract_sub_mapping(&[0, 1], &[0])?,
866 operating_dt: op.operating_dt,
867 },
868 &[a, b],
869 )?;
870
871 let a_i32 = patch.wire_node(format!("{name}.a_as_i32"), cast(i32::datum_type()), &[a])?[0];
872 let b_i32 = patch.wire_node(format!("{name}.b_as_i32"), cast(i32::datum_type()), &[b])?[0];
873 let sum_a = patch.wire_node(
874 format!("{name}.sum_a"),
875 Reduce::new(tvec!(k_axis.inputs[0][0]), Reducer::Sum),
876 &[a_i32],
877 )?;
878 let sum_b = patch.wire_node(
879 format!("{name}.sum_b"),
880 Reduce::new(tvec!(k_axis.inputs[1][0]), Reducer::Sum),
881 &[b_i32],
882 )?;
883
884 let sum_a =
885 wire_axes_fix(&mut patch, name, "sum_a", &op.axes.extract_sub_mapping(&[0], &[0])?, sum_a)?;
886 let sum_b =
887 wire_axes_fix(&mut patch, name, "sum_b", &op.axes.extract_sub_mapping(&[1], &[0])?, sum_b)?;
888 let bias = tvec!(bias);
889 let bias =
890 wire_axes_fix(&mut patch, name, "bias", &op.axes.extract_sub_mapping(&[2], &[0])?, bias)?;
891
892 let abc_scale = combine_scales(&mut patch, name, a_scale, b_scale, c_scale)?;
893
894 output = patch.wire_node(format!("{name}.add_bias"), add(), &[output[0], bias[0]])?;
895
896 let k = model.outlet_fact(node.inputs[0])?.shape[k_axis.inputs[0][0]].clone();
897 let output = compensate_zero_points(&mut patch, name, output[0], k, a0, b0, sum_a[0], sum_b[0])
898 .context("Zero point compensation")?;
899 let output = requant(&mut patch, name, output, op.q_params.unwrap(), abc_scale, c0)?;
900 patch.shunt_outside(model, node.id.into(), output)?;
901 Ok(patch)
902}
903
904fn flatten_rule(
905 _ctx: &(),
906 model: &TypedModel,
907 node: &TypedNode,
908 _name: &str,
909 op: &EinSumMatMul,
910) -> TractResult<Option<TypedModelPatch>> {
911 TypedModelPatch::replace_single_op(model, node, &node.inputs, op.op.clone()).map(Some)
912}
913
914fn optimized_mat_mul(
915 model: &TypedModel,
916 node: &TypedNode,
917 op: &EinSumMatMul,
918) -> TractResult<Option<TypedModelPatch>> {
919 let (mode_picker, left_pack, impls) = kernel_selection::strategize(model, node, op)?;
920 let input_facts = model.node_input_facts(node.id)?;
921 let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;
922 let prefix = &node.name;
923
924 let mut patch = TypedModelPatch::new("Einsum to OptMatMul");
925 let taps = patch.taps(model, &node.inputs)?;
926 let name = &node.name;
927
928 let pack_a: Box<dyn TypedOp> = if input_facts[0].konst.is_some() {
929 if let Some(packed_format) = left_pack.downcast_ref::<PackedBlockQuantFormat>().cloned() {
930 Box::new(OptSimpleMatMulPack {
931 packed_format,
932 k: input_shapes[0][op.a_k()].to_usize().unwrap(),
933 m: input_shapes[0][op.a_m()].to_usize().unwrap(),
934 })
935 } else {
936 Box::new(OptMatMulPack {
939 packers: vec![left_pack],
940 mode_picker: ModePicker::Single,
941 k_axis: op.a_k(),
942 mn_axis: op.a_m(),
943 })
944 }
945 } else {
946 Box::new(OptMatMulPack {
947 packers: impls
948 .iter()
949 .map(|(mmm, p, pe)| {
950 pe.as_ref()
951 .map(|pe| pe.from.clone())
952 .unwrap_or_else(|| mmm.packings()[*p].0.clone())
953 })
954 .collect(),
955 mode_picker: mode_picker.clone(),
956 k_axis: op.a_k(),
957 mn_axis: op.a_m(),
958 })
959 };
960 let pa = patch.wire_node(format!("{prefix}.pack_a"), pack_a, &[taps[0]])?[0];
961
962 let pb = patch.wire_node(
963 format!("{prefix}.pack_b"),
964 OptMatMulPack {
965 k_axis: op.b_k(),
966 mn_axis: op.b_n(),
967 packers: impls.iter().map(|(mmm, p, _)| mmm.packings()[*p].1.clone()).collect(),
968 mode_picker: mode_picker.clone(),
969 },
970 &[taps[1]],
971 )?[0];
972
973 let mut c_to_a_axis_mapping = tvec!();
974 let mut c_to_b_axis_mapping = tvec!();
975 for axis in op
976 .op
977 .axes
978 .iter_all_axes()
979 .filter(|&axis| ![op.m_axis, op.k_axis, op.n_axis].contains(&axis.repr))
980 {
981 if let (&[c], &[a]) = (&*axis.outputs[0], &*axis.inputs[0])
982 && input_shapes[0][a] != 1.to_dim()
983 {
984 let a = a - (a > op.a_m()) as usize - (a > op.a_k()) as usize;
985 c_to_a_axis_mapping.push((c, a));
986 }
987 if let (&[c], &[b]) = (&*axis.outputs[0], &*axis.inputs[1])
988 && input_shapes[1][b] != 1.to_dim()
989 {
990 let b = b - (b > op.b_n()) as usize - (b > op.b_k()) as usize;
991 c_to_b_axis_mapping.push((c, b));
992 }
993 }
994
995 let c_fact = op.output_facts(&input_facts)?.remove(0);
996 let geo = AddMatMulGeometry {
997 k: op.k.clone(),
998 c_to_a_axis_mapping: MapOutputAxisToInput(c_to_a_axis_mapping),
999 c_to_b_axis_mapping: MapOutputAxisToInput(c_to_b_axis_mapping),
1000 };
1001 let (mmms, packings, extractor): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(impls);
1002 let outputs = mmms.iter().map(|mmm| unsafe { mmm.c_view(op.c_m(), op.c_n()) }).collect();
1003 let trivial_packing = mmms.len() == 1
1004 && packings[0] == 0
1005 && extractor[0].is_none()
1006 && input_facts[0].exotic_fact.is_none();
1007 let opt = OptMatMul::new(
1008 mmms,
1009 mode_picker,
1010 c_fact,
1011 op.c_m(),
1012 op.c_n(),
1013 vec![
1014 ProtoFusedSpec::AddMatMul {
1015 geo,
1016 a: MatMulOperand::Input(0),
1017 b: MatMulOperand::Input(1),
1018 packings: izip!(packings, extractor).collect_vec(),
1019 },
1020 ProtoFusedSpec::Store(outputs),
1021 ],
1022 trivial_packing,
1023 )
1024 .context("Creating OptMatMul")?;
1025 let output = patch.wire_node(name, opt, &[pa, pb])?[0];
1026 patch.shunt_outside(model, node.id.into(), output)?;
1027 Ok(Some(patch))
1028}