onnxscript 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- onnxscript/__init__.py +131 -0
- onnxscript/_framework_apis/__init__.py +3 -0
- onnxscript/_framework_apis/torch_2_5.py +117 -0
- onnxscript/_framework_apis/torch_2_6.py +45 -0
- onnxscript/_internal/__init__.py +0 -0
- onnxscript/_internal/analysis.py +229 -0
- onnxscript/_internal/ast_utils.py +64 -0
- onnxscript/_internal/autocast.py +250 -0
- onnxscript/_internal/deprecation.py +78 -0
- onnxscript/_internal/param_manipulation.py +148 -0
- onnxscript/_internal/runtime_typing.py +43 -0
- onnxscript/_internal/utils.py +99 -0
- onnxscript/_internal/version_utils.py +118 -0
- onnxscript/_legacy_ir/__init__.py +341 -0
- onnxscript/_legacy_ir/visitor.py +937 -0
- onnxscript/_thirdparty/asciichartpy.py +313 -0
- onnxscript/backend/__init__.py +2 -0
- onnxscript/backend/onnx_backend.py +303 -0
- onnxscript/backend/onnx_export.py +885 -0
- onnxscript/converter.py +1470 -0
- onnxscript/evaluator.py +619 -0
- onnxscript/function_libs/tools/torch_lib/deduce_type_constraints.py +403 -0
- onnxscript/function_libs/tools/torch_lib/generate_aten_signatures.py +333 -0
- onnxscript/function_libs/tools/torch_lib/generate_prims_signatures.py +331 -0
- onnxscript/function_libs/torch_lib/__init__.py +12 -0
- onnxscript/function_libs/torch_lib/_constants.py +5 -0
- onnxscript/function_libs/torch_lib/_flags.py +58 -0
- onnxscript/function_libs/torch_lib/graph_building/__init__.py +56 -0
- onnxscript/function_libs/torch_lib/graph_building/_graph_building_ir.py +723 -0
- onnxscript/function_libs/torch_lib/graph_building/_graph_building_torch.py +1125 -0
- onnxscript/function_libs/torch_lib/ops/__init__.py +27 -0
- onnxscript/function_libs/torch_lib/ops/common.py +80 -0
- onnxscript/function_libs/torch_lib/ops/core.py +8935 -0
- onnxscript/function_libs/torch_lib/ops/fft.py +385 -0
- onnxscript/function_libs/torch_lib/ops/linalg.py +399 -0
- onnxscript/function_libs/torch_lib/ops/nested.py +25 -0
- onnxscript/function_libs/torch_lib/ops/nn.py +2713 -0
- onnxscript/function_libs/torch_lib/ops/prims.py +850 -0
- onnxscript/function_libs/torch_lib/ops/quantized_decomposed.py +63 -0
- onnxscript/function_libs/torch_lib/ops/sparse.py +23 -0
- onnxscript/function_libs/torch_lib/ops/special.py +387 -0
- onnxscript/function_libs/torch_lib/ops/vision.py +25 -0
- onnxscript/function_libs/torch_lib/registration.py +151 -0
- onnxscript/function_libs/torch_lib/tensor_typing.py +74 -0
- onnxscript/ir/__init__.py +153 -0
- onnxscript/ir/_convenience.py +447 -0
- onnxscript/ir/_core.py +3119 -0
- onnxscript/ir/_display.py +49 -0
- onnxscript/ir/_enums.py +163 -0
- onnxscript/ir/_graph_comparison.py +23 -0
- onnxscript/ir/_io.py +97 -0
- onnxscript/ir/_linked_list.py +276 -0
- onnxscript/ir/_metadata.py +44 -0
- onnxscript/ir/_name_authority.py +72 -0
- onnxscript/ir/_polyfill.py +25 -0
- onnxscript/ir/_protocols.py +598 -0
- onnxscript/ir/_schemas.py +548 -0
- onnxscript/ir/_tape.py +120 -0
- onnxscript/ir/_type_casting.py +106 -0
- onnxscript/ir/convenience.py +32 -0
- onnxscript/ir/external_data.py +396 -0
- onnxscript/ir/passes/__init__.py +33 -0
- onnxscript/ir/passes/_pass_infra.py +172 -0
- onnxscript/ir/serde.py +1620 -0
- onnxscript/ir/tensor_adapters.py +122 -0
- onnxscript/ir/traversal.py +82 -0
- onnxscript/irbuilder.py +542 -0
- onnxscript/main.py +167 -0
- onnxscript/onnx_opset/__init__.py +232 -0
- onnxscript/onnx_opset/_impl/opset1.py +4100 -0
- onnxscript/onnx_opset/_impl/opset10.py +1227 -0
- onnxscript/onnx_opset/_impl/opset11.py +4013 -0
- onnxscript/onnx_opset/_impl/opset12.py +1078 -0
- onnxscript/onnx_opset/_impl/opset13.py +3924 -0
- onnxscript/onnx_opset/_impl/opset14.py +999 -0
- onnxscript/onnx_opset/_impl/opset15.py +604 -0
- onnxscript/onnx_opset/_impl/opset16.py +1255 -0
- onnxscript/onnx_opset/_impl/opset17.py +561 -0
- onnxscript/onnx_opset/_impl/opset18.py +1803 -0
- onnxscript/onnx_opset/_impl/opset19.py +1942 -0
- onnxscript/onnx_opset/_impl/opset2.py +218 -0
- onnxscript/onnx_opset/_impl/opset20.py +675 -0
- onnxscript/onnx_opset/_impl/opset21.py +1976 -0
- onnxscript/onnx_opset/_impl/opset22.py +2588 -0
- onnxscript/onnx_opset/_impl/opset3.py +199 -0
- onnxscript/onnx_opset/_impl/opset4.py +77 -0
- onnxscript/onnx_opset/_impl/opset5.py +84 -0
- onnxscript/onnx_opset/_impl/opset6.py +944 -0
- onnxscript/onnx_opset/_impl/opset7.py +1243 -0
- onnxscript/onnx_opset/_impl/opset8.py +444 -0
- onnxscript/onnx_opset/_impl/opset9.py +1485 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_ml1.py +974 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_ml2.py +112 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_ml3.py +308 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_ml4.py +129 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_ml5.py +158 -0
- onnxscript/onnx_opset/_impl/opset_ai_onnx_preview_training1.py +577 -0
- onnxscript/onnx_types.py +229 -0
- onnxscript/optimizer/__init__.py +39 -0
- onnxscript/optimizer/_constant_folding.py +1083 -0
- onnxscript/optimizer/_inliner.py +312 -0
- onnxscript/optimizer/_legacy/_optimizer.py +98 -0
- onnxscript/optimizer/_legacy/_remove_unused_proto.py +144 -0
- onnxscript/optimizer/_legacy/_simple_function_folding.py +243 -0
- onnxscript/optimizer/_legacy/constant_folding.py +293 -0
- onnxscript/optimizer/_legacy/evaluator.py +439 -0
- onnxscript/optimizer/_optimizer.py +61 -0
- onnxscript/optimizer/_remove_unused.py +106 -0
- onnxscript/optimizer/_remove_unused_function.py +72 -0
- onnxscript/py.typed +1 -0
- onnxscript/rewriter/__init__.py +56 -0
- onnxscript/rewriter/_ir_utils.py +111 -0
- onnxscript/rewriter/broadcast_to_matmul.py +180 -0
- onnxscript/rewriter/cast_constant_of_shape.py +50 -0
- onnxscript/rewriter/collapse_slices.py +141 -0
- onnxscript/rewriter/erfgelu.py +27 -0
- onnxscript/rewriter/function_rule.py +232 -0
- onnxscript/rewriter/gemm_to_matmul_add.py +21 -0
- onnxscript/rewriter/generic_pattern.py +700 -0
- onnxscript/rewriter/llama_rule_sets.py +287 -0
- onnxscript/rewriter/no_op.py +56 -0
- onnxscript/rewriter/onnxruntime/__init__.py +50 -0
- onnxscript/rewriter/onnxruntime/bfloat16_utils/bfloat16_converter.py +99 -0
- onnxscript/rewriter/onnxruntime/fused_matmul_rule_sets.py +177 -0
- onnxscript/rewriter/onnxruntime/group_normalization_merge_silu.py +64 -0
- onnxscript/rewriter/onnxruntime/instance_to_group_normalization.py +155 -0
- onnxscript/rewriter/onnxruntime/softmax.py +63 -0
- onnxscript/rewriter/onnxruntime/transformers/__init__.py +21 -0
- onnxscript/rewriter/onnxruntime/transformers/biassplitgelu.py +31 -0
- onnxscript/rewriter/onnxruntime/transformers/fastgelu.py +29 -0
- onnxscript/rewriter/onnxruntime/transformers/layernorm.py +47 -0
- onnxscript/rewriter/onnxruntime/transformers/multihead_attention.py +715 -0
- onnxscript/rewriter/ort_fusions/__init__.py +9 -0
- onnxscript/rewriter/ort_fusions/_core.py +28 -0
- onnxscript/rewriter/ort_fusions/_smollm_1.py +253 -0
- onnxscript/rewriter/ort_fusions/_smollm_2.py +467 -0
- onnxscript/rewriter/ort_fusions/_test_models.py +122 -0
- onnxscript/rewriter/ort_fusions/_test_utils.py +42 -0
- onnxscript/rewriter/ort_fusions/cos_sin_cache.py +154 -0
- onnxscript/rewriter/ort_fusions/gqa.py +156 -0
- onnxscript/rewriter/ort_fusions/mha.py +198 -0
- onnxscript/rewriter/ort_fusions/rms_normalization.py +95 -0
- onnxscript/rewriter/ort_fusions/rotary_embedding.py +64 -0
- onnxscript/rewriter/ort_fusions/sdpa.py +75 -0
- onnxscript/rewriter/ort_fusions/skip_normalization.py +46 -0
- onnxscript/rewriter/pattern.py +1714 -0
- onnxscript/rewriter/testing.py +77 -0
- onnxscript/sourceinfo.py +59 -0
- onnxscript/tensor.py +227 -0
- onnxscript/testing/__init__.py +482 -0
- onnxscript/tools/__init__.py +4 -0
- onnxscript/tools/benchmark/__init__.py +23 -0
- onnxscript/tools/benchmark/benchmark_helpers.py +783 -0
- onnxscript/tools/benchmark/benchmark_run.py +140 -0
- onnxscript/tools/benchmark/export_model.py +207 -0
- onnxscript/tools/benchmark/export_model_batch.py +146 -0
- onnxscript/tools/memory_peak.py +244 -0
- onnxscript/tools/training_helper.py +50 -0
- onnxscript/tools/transformers_models/__init__.py +190 -0
- onnxscript/tools/transformers_models/llama.py +168 -0
- onnxscript/tools/transformers_models/mistral.py +238 -0
- onnxscript/tools/transformers_models/phi.py +248 -0
- onnxscript/tools/transformers_models/phi3.py +259 -0
- onnxscript/type_annotation.py +281 -0
- onnxscript/utils/__init__.py +0 -0
- onnxscript/utils/evaluation_utils.py +56 -0
- onnxscript/utils/timing_utils.py +33 -0
- onnxscript/utils/utils.py +84 -0
- onnxscript/values.py +790 -0
- onnxscript/version_converter/__init__.py +21 -0
- onnxscript/version_converter/_version_converter.py +314 -0
- onnxscript-0.1.0.dist-info/LICENSE +21 -0
- onnxscript-0.1.0.dist-info/METADATA +370 -0
- onnxscript-0.1.0.dist-info/RECORD +176 -0
- onnxscript-0.1.0.dist-info/WHEEL +5 -0
- onnxscript-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import onnxscript.ir as ir
|
|
8
|
+
from onnxscript.optimizer import remove_unused_nodes
|
|
9
|
+
from onnxscript.rewriter import _ir_utils, pattern
|
|
10
|
+
|
|
11
|
+
# Rewrite the computation of cos/sin cache into the form expected by ORT's custom ops.
|
|
12
|
+
|
|
13
|
+
# We match against the following code pattern:
|
|
14
|
+
# Original code (from transformers) for computing cos/sin cache for RoPE:
|
|
15
|
+
# https://github.com/huggingface/transformers/blob/0ade1caa356dce6b70ef8293addeb0898f177206/src/transformers/models/llama/modeling_llama.py#L135
|
|
16
|
+
# position_ids_expanded = position_ids[:, None, :].float()
|
|
17
|
+
# freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
|
|
18
|
+
# emb = torch.cat((freqs, freqs), dim=-1)
|
|
19
|
+
# cos = emb.cos()
|
|
20
|
+
# sin = emb.sin()
|
|
21
|
+
#
|
|
22
|
+
# We rewrite this pattern into the following form:
|
|
23
|
+
# inv_freq_values = inv_freq_expanded.reshape(1, -1)
|
|
24
|
+
# pos_id_range = np.arange(max_pos_id, dtype=np.float32).reshape(-1, 1)
|
|
25
|
+
# angles = np.matmul(pos_id_range, inv_freq_values)
|
|
26
|
+
# cos_value = np.cos(angles)
|
|
27
|
+
# sin_value = np.sin(angles)
|
|
28
|
+
# cos_2d = op.Constant(value=ir.tensor(cos_value))
|
|
29
|
+
# sin_2d = op.Constant(value=ir.tensor(sin_value))
|
|
30
|
+
#
|
|
31
|
+
# This produces cos/sin values in a form that can be used by ORT's custom ops.
|
|
32
|
+
|
|
33
|
+
# TODO: To apply the pattern-rewrite, we need to know the maximum position id.
|
|
34
|
+
# Need to find a way to get this information from the model or its config.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CosSinCacheFusion(pattern.RewriteRuleClassBase):
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
name: str,
|
|
41
|
+
max_pos_id: int,
|
|
42
|
+
*,
|
|
43
|
+
cast: bool = False,
|
|
44
|
+
reshape: bool = False,
|
|
45
|
+
const_freqs: bool = False,
|
|
46
|
+
):
|
|
47
|
+
# This pattern makes use of shared Cos/Sin values. So, we can't remove the
|
|
48
|
+
# matched nodes as part of the rewrite-step. We apply a separate final
|
|
49
|
+
# pass to remove unused nodes.
|
|
50
|
+
super().__init__(name, remove_nodes=False)
|
|
51
|
+
self._max_pos_id = max_pos_id
|
|
52
|
+
# map from inv_freq to (cos, sin) values for transformed graph
|
|
53
|
+
self._inv_freq_cos_sin_cache: dict[ir.Value, tuple[ir.Value, ir.Value]] = {}
|
|
54
|
+
self._reshape = reshape
|
|
55
|
+
self._cast = cast
|
|
56
|
+
self._const_freqs = const_freqs
|
|
57
|
+
|
|
58
|
+
def cleanup(self):
|
|
59
|
+
self._inv_freq_cos_sin_cache.clear()
|
|
60
|
+
|
|
61
|
+
def pattern(self, op, x, inv_freq, position_ids, interleaved, num_heads, freqs, dtype):
|
|
62
|
+
if not self._const_freqs:
|
|
63
|
+
# Compute freqs from inv_freq and position_ids. In the _const_freqs case,
|
|
64
|
+
# this computation has been constant-folded away and freqs is a constant.
|
|
65
|
+
# B: batch size, S: sequence length, E: embedding dimension
|
|
66
|
+
# position_ids: [B, S]
|
|
67
|
+
# inv_freq: [1, E, 1]
|
|
68
|
+
position_ids_expanded = op.Unsqueeze(position_ids, 1) # [B, S] => [B, 1, S]
|
|
69
|
+
position_ids_expanded = op.Cast(position_ids_expanded, to=ir.DataType.FLOAT)
|
|
70
|
+
# if self._reshape:
|
|
71
|
+
# position_ids_expanded = op.Expand(position_ids_expanded, _allow_other_inputs=True)
|
|
72
|
+
# position_ids_expanded = op.Reshape(position_ids_expanded, _allow_other_inputs=True)
|
|
73
|
+
freqs = op.MatMul(inv_freq, position_ids_expanded) # [B, E, S]
|
|
74
|
+
# if self._reshape:
|
|
75
|
+
# freqs = op.Reshape(freqs, freqs_3d_shape) # redundant reshape
|
|
76
|
+
freqs = op.Transpose(freqs, perm=[0, 2, 1]) # [B, S, E]
|
|
77
|
+
emb = op.Concat(freqs, freqs, axis=-1)
|
|
78
|
+
cos = op.Cos(emb)
|
|
79
|
+
if self._cast:
|
|
80
|
+
cos = op.Cast(cos, to=dtype)
|
|
81
|
+
sin = op.Sin(emb)
|
|
82
|
+
if self._cast:
|
|
83
|
+
sin = op.Cast(sin, to=dtype)
|
|
84
|
+
cos_4d = op.Unsqueeze(cos, 1) # convert
|
|
85
|
+
sin_4d = op.Unsqueeze(sin, 1)
|
|
86
|
+
return op.RotaryEmbedding(
|
|
87
|
+
x,
|
|
88
|
+
cos_4d,
|
|
89
|
+
sin_4d,
|
|
90
|
+
interleaved=interleaved,
|
|
91
|
+
num_heads=num_heads,
|
|
92
|
+
_domain="ai.onnxruntime.fusion",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def check(self, context, inv_freq, position_ids, freqs, **_):
|
|
96
|
+
# TODO(rama): handle redundant reshape/expand
|
|
97
|
+
if self._const_freqs:
|
|
98
|
+
return (freqs.const_value is not None) and _ir_utils.has_rank(freqs, 3)
|
|
99
|
+
if not _ir_utils.has_rank(position_ids, 2):
|
|
100
|
+
return False
|
|
101
|
+
if not _ir_utils.has_rank(inv_freq, 3):
|
|
102
|
+
return False
|
|
103
|
+
inv_freq_shape = inv_freq.shape
|
|
104
|
+
if inv_freq.const_value is None: # TODO: should this be inv_freq_shape?
|
|
105
|
+
return False
|
|
106
|
+
return inv_freq_shape[0] == 1 and inv_freq_shape[2] == 1
|
|
107
|
+
|
|
108
|
+
def rewrite(
|
|
109
|
+
self, op, x, inv_freq, position_ids, interleaved, num_heads, freqs, dtype, **_
|
|
110
|
+
):
|
|
111
|
+
if inv_freq in self._inv_freq_cos_sin_cache:
|
|
112
|
+
cos_2d, sin_2d = self._inv_freq_cos_sin_cache[inv_freq]
|
|
113
|
+
else:
|
|
114
|
+
if self._const_freqs:
|
|
115
|
+
angles = freqs.const_value.numpy()
|
|
116
|
+
else:
|
|
117
|
+
inv_freq_values = inv_freq.const_value.numpy().reshape(1, -1)
|
|
118
|
+
pos_id_range = np.arange(self._max_pos_id, dtype=np.float32).reshape(-1, 1)
|
|
119
|
+
angles = np.matmul(pos_id_range, inv_freq_values)
|
|
120
|
+
cos_value = np.cos(angles)
|
|
121
|
+
sin_value = np.sin(angles)
|
|
122
|
+
cos_2d = op.Constant(value=ir.tensor(cos_value))
|
|
123
|
+
sin_2d = op.Constant(value=ir.tensor(sin_value))
|
|
124
|
+
if self._cast:
|
|
125
|
+
cos_2d = op.Cast(cos_2d, to=dtype)
|
|
126
|
+
sin_2d = op.Cast(sin_2d, to=dtype)
|
|
127
|
+
self._inv_freq_cos_sin_cache[inv_freq] = (cos_2d, sin_2d)
|
|
128
|
+
return op.RotaryEmbedding(
|
|
129
|
+
x,
|
|
130
|
+
position_ids,
|
|
131
|
+
cos_2d,
|
|
132
|
+
sin_2d,
|
|
133
|
+
interleaved=interleaved,
|
|
134
|
+
num_heads=num_heads,
|
|
135
|
+
_domain="com.microsoft",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_cast = CosSinCacheFusion.rule("CosSinCache", 2048, cast=True, const_freqs=True)
|
|
140
|
+
_no_cast = CosSinCacheFusion.rule("CosSinCache", 2048, cast=False)
|
|
141
|
+
|
|
142
|
+
cos_sin_cache_rules = pattern.RewriteRuleSet([_cast, _no_cast])
|
|
143
|
+
|
|
144
|
+
debug: bool = True
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def fuse_cos_sin_cache(model: ir.Model) -> int:
|
|
148
|
+
count = cos_sin_cache_rules.apply_to_model(model)
|
|
149
|
+
if count == 0 and debug:
|
|
150
|
+
cos_sin_cache_rules.apply_to_model(model, debug=True)
|
|
151
|
+
else:
|
|
152
|
+
print(f"CosSinCache count: {count}")
|
|
153
|
+
remove_unused_nodes(model)
|
|
154
|
+
return count
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import onnxscript.ir as ir
|
|
6
|
+
from onnxscript.optimizer import remove_unused_nodes
|
|
7
|
+
from onnxscript.rewriter import pattern
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GroupQueryAttention(pattern.RewriteRuleClassBase):
|
|
11
|
+
def __init__(self, name: str, *, use_2d_matmul: bool):
|
|
12
|
+
super().__init__(name, remove_nodes=False)
|
|
13
|
+
self._use_2d_matmul = use_2d_matmul
|
|
14
|
+
|
|
15
|
+
def _compute_packed_QKV(self, op, input, weight):
|
|
16
|
+
if self._use_2d_matmul:
|
|
17
|
+
# Convert batched input of shape (B, S, D) to 2D input (B*S, D)
|
|
18
|
+
input = op.Reshape(input, _allow_other_inputs=True)
|
|
19
|
+
projected = op.MatMul(input, weight)
|
|
20
|
+
if self._use_2d_matmul:
|
|
21
|
+
# Convert 2D output back to batched output of shape (B, S, D)
|
|
22
|
+
projected = op.Reshape(projected, _allow_other_inputs=True)
|
|
23
|
+
# Split combined QKV into Q, K, and V
|
|
24
|
+
query_3d = op.Slice(projected, _allow_other_inputs=True)
|
|
25
|
+
key_3d = op.Slice(projected, _allow_other_inputs=True)
|
|
26
|
+
value_3d = op.Slice(projected, _allow_other_inputs=True)
|
|
27
|
+
# Reshape from (B, S, D) to (B, S, H, D/H)
|
|
28
|
+
query_4d = op.Reshape(
|
|
29
|
+
query_3d,
|
|
30
|
+
_allow_other_inputs=True,
|
|
31
|
+
_allow_other_attributes=True,
|
|
32
|
+
_outputs=["query_mm_reshaped"],
|
|
33
|
+
)
|
|
34
|
+
# Transpose from (B, S, H, D/H) to (B, H, S, D/H)
|
|
35
|
+
query = op.Transpose(query_4d, perm=[0, 2, 1, 3])
|
|
36
|
+
key_4d = op.Reshape(
|
|
37
|
+
key_3d,
|
|
38
|
+
_allow_other_inputs=True,
|
|
39
|
+
_allow_other_attributes=True,
|
|
40
|
+
_outputs=["key_mm_reshaped"],
|
|
41
|
+
)
|
|
42
|
+
key = op.Transpose(key_4d, perm=[0, 2, 1, 3])
|
|
43
|
+
value_4d = op.Reshape(
|
|
44
|
+
value_3d,
|
|
45
|
+
_allow_other_inputs=True,
|
|
46
|
+
_allow_other_attributes=True,
|
|
47
|
+
_outputs=["value_mm_reshaped"],
|
|
48
|
+
)
|
|
49
|
+
value = op.Transpose(value_4d, perm=[0, 2, 1, 3])
|
|
50
|
+
|
|
51
|
+
return query, key, value
|
|
52
|
+
|
|
53
|
+
def pattern(
|
|
54
|
+
self,
|
|
55
|
+
op,
|
|
56
|
+
input,
|
|
57
|
+
qkv_weight,
|
|
58
|
+
mask,
|
|
59
|
+
cos,
|
|
60
|
+
sin,
|
|
61
|
+
past_key,
|
|
62
|
+
past_value,
|
|
63
|
+
position_ids,
|
|
64
|
+
):
|
|
65
|
+
query, key, value = self._compute_packed_QKV(op, input, qkv_weight)
|
|
66
|
+
|
|
67
|
+
query_rope = op.RotaryEmbedding(query, position_ids, cos, sin, _domain="com.microsoft")
|
|
68
|
+
|
|
69
|
+
key_rope = op.RotaryEmbedding(key, position_ids, cos, sin, _domain="com.microsoft")
|
|
70
|
+
present_key = op.Concat(past_key, key_rope, axis=-2)
|
|
71
|
+
# Transpose last two axes of present_key to compute dot-product via matmul.
|
|
72
|
+
present_key = op.Transpose(present_key, perm=[0, 1, 3, 2])
|
|
73
|
+
|
|
74
|
+
present_value = op.Concat(past_value, value, axis=-2)
|
|
75
|
+
|
|
76
|
+
attention = op.SDPA(
|
|
77
|
+
query_rope, present_key, present_value, mask, _domain="ai.onnxruntime.fusion"
|
|
78
|
+
)
|
|
79
|
+
# Transpose back to (B, S, H, D/H)
|
|
80
|
+
attention_transposed = op.Transpose(attention, perm=[0, 2, 1, 3])
|
|
81
|
+
# Reshape back to (B, S, D)
|
|
82
|
+
attention_reshaped = op.Reshape(
|
|
83
|
+
attention_transposed, _allow_other_inputs=True, _outputs=["attention_reshaped"]
|
|
84
|
+
)
|
|
85
|
+
return attention_reshaped, present_key, present_value
|
|
86
|
+
|
|
87
|
+
def check(
|
|
88
|
+
self,
|
|
89
|
+
op,
|
|
90
|
+
# query_mm_reshaped,
|
|
91
|
+
# key_mm_reshaped,
|
|
92
|
+
# value_mm_reshaped,
|
|
93
|
+
# key_reshaped,
|
|
94
|
+
# key_transposed,
|
|
95
|
+
# attention_reshaped,
|
|
96
|
+
**_,
|
|
97
|
+
):
|
|
98
|
+
# bindings: dict[str, int] = {}
|
|
99
|
+
# status = (
|
|
100
|
+
# _check_shape(bindings, query_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
101
|
+
# and _check_shape(bindings, key_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
102
|
+
# and _check_shape(bindings, value_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
103
|
+
# and _check_shape(bindings, key_reshaped, ["B*H", "KVS", "d_h"])
|
|
104
|
+
# and _check_shape(bindings, key_transposed, ["B", "H", "d_h", "KVS"])
|
|
105
|
+
# and _check_shape(bindings, attention_reshaped, ["B", "S", "H*d_h"])
|
|
106
|
+
# )
|
|
107
|
+
# if not status:
|
|
108
|
+
# return False
|
|
109
|
+
# if bindings["B"] * bindings["H"] != bindings["B*H"]:
|
|
110
|
+
# return False
|
|
111
|
+
# if bindings["H"] * bindings["d_h"] != bindings["H*d_h"]:
|
|
112
|
+
# return False
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
def rewrite(
|
|
116
|
+
self,
|
|
117
|
+
op,
|
|
118
|
+
input,
|
|
119
|
+
qkv_weight,
|
|
120
|
+
mask,
|
|
121
|
+
cos,
|
|
122
|
+
sin,
|
|
123
|
+
past_key,
|
|
124
|
+
past_value,
|
|
125
|
+
position_ids,
|
|
126
|
+
query_mm_reshaped,
|
|
127
|
+
**_,
|
|
128
|
+
):
|
|
129
|
+
num_heads = query_mm_reshaped.shape[2]
|
|
130
|
+
qkv = op.MatMul(input, qkv_weight)
|
|
131
|
+
return op.GroupQueryAttention(
|
|
132
|
+
qkv,
|
|
133
|
+
None, # key
|
|
134
|
+
None, # value
|
|
135
|
+
past_key,
|
|
136
|
+
past_value,
|
|
137
|
+
# seqlens_k,
|
|
138
|
+
# total_sequence_length,
|
|
139
|
+
cos,
|
|
140
|
+
sin,
|
|
141
|
+
num_heads=num_heads,
|
|
142
|
+
_domain="com.microsoft",
|
|
143
|
+
_outputs=3,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
_rule1 = GroupQueryAttention.rule("MHA_2dmm", use_2d_matmul=False)
|
|
148
|
+
|
|
149
|
+
gqa_rules = pattern.RewriteRuleSet([_rule1])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def fuse_gqa(model: ir.Model) -> int:
|
|
153
|
+
count = gqa_rules.apply_to_model(model)
|
|
154
|
+
print(f"GQA count: {count}")
|
|
155
|
+
remove_unused_nodes(model)
|
|
156
|
+
return count
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
import onnxscript.ir as ir
|
|
8
|
+
from onnxscript.rewriter import pattern
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
The MultiHeadAttention pattern:
|
|
12
|
+
|
|
13
|
+
B: Batch size
|
|
14
|
+
S: Sequence length
|
|
15
|
+
D: input embedding dimension
|
|
16
|
+
H: number of heads
|
|
17
|
+
d_h: head size (usually, D = H * d_h)
|
|
18
|
+
|
|
19
|
+
thus, weights are usually of shape (D, D) and (D, D) and (D, D)
|
|
20
|
+
|
|
21
|
+
for each of Q, K, and V, we have the following pattern:
|
|
22
|
+
MatMul (Input, W), producing output of shape (B, S, D)
|
|
23
|
+
Reshape to produce a matrix of shape (B, S, H, d_h)
|
|
24
|
+
Transpose middle two axes to produce a matrix of shape (B, H, S, d_h)
|
|
25
|
+
|
|
26
|
+
This is followed by a RotaryEmbedding pattern for Q and K
|
|
27
|
+
|
|
28
|
+
The last two axes of the key-embedding are then swapped (using a Reshape/Transpose/Reshape sequence)
|
|
29
|
+
|
|
30
|
+
The dot-product attention is then computed using SDPA.
|
|
31
|
+
Finally, the output is transposed and reshaped back to (B, S, D) shape
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _check_shape(bindings: dict[str, int], val: ir.Value, shape: Sequence[str]) -> bool:
|
|
36
|
+
if val.shape is None:
|
|
37
|
+
return False
|
|
38
|
+
if val.shape.rank() != len(shape):
|
|
39
|
+
return False
|
|
40
|
+
for actual, expected in zip(val.shape, shape):
|
|
41
|
+
if expected not in bindings:
|
|
42
|
+
bindings[expected] = actual # type: ignore[assignment]
|
|
43
|
+
elif actual != bindings[expected]:
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MultiHeadAttention(pattern.RewriteRuleClassBase):
|
|
49
|
+
def __init__(self, name: str, *, use_2d_matmul: bool):
|
|
50
|
+
super().__init__(name)
|
|
51
|
+
self._use_2d_matmul = use_2d_matmul
|
|
52
|
+
|
|
53
|
+
def _compute_QKV(self, op, input, weight, reshape_var: str):
|
|
54
|
+
"""Applied to generate each of Q, K, and V from input."""
|
|
55
|
+
if self._use_2d_matmul:
|
|
56
|
+
# Convert batched input of shape (B, S, D) to 2D input (B*S, D)
|
|
57
|
+
input = op.Reshape(input, _allow_other_inputs=True)
|
|
58
|
+
projected = op.MatMul(input, weight)
|
|
59
|
+
if self._use_2d_matmul:
|
|
60
|
+
# Convert 2D output back to batched output of shape (B, S, D)
|
|
61
|
+
projected = op.Reshape(projected, _allow_other_inputs=True)
|
|
62
|
+
# Reshape from (B, S, D) to (B, S, H, D/H)
|
|
63
|
+
reshaped = op.Reshape(
|
|
64
|
+
projected,
|
|
65
|
+
_allow_other_inputs=True,
|
|
66
|
+
_allow_other_attributes=True,
|
|
67
|
+
_outputs=[reshape_var],
|
|
68
|
+
)
|
|
69
|
+
# Transpose from (B, S, H, D/H) to (B, H, S, D/H)
|
|
70
|
+
transposed = op.Transpose(reshaped, perm=[0, 2, 1, 3])
|
|
71
|
+
return transposed
|
|
72
|
+
|
|
73
|
+
def pattern(
|
|
74
|
+
self,
|
|
75
|
+
op,
|
|
76
|
+
input,
|
|
77
|
+
query_weight,
|
|
78
|
+
key_weight,
|
|
79
|
+
value_weight,
|
|
80
|
+
qkv_weight,
|
|
81
|
+
mask,
|
|
82
|
+
cos,
|
|
83
|
+
sin,
|
|
84
|
+
past_key,
|
|
85
|
+
past_value,
|
|
86
|
+
position_ids,
|
|
87
|
+
):
|
|
88
|
+
query = self._compute_QKV(op, input, query_weight, "query_mm_reshaped")
|
|
89
|
+
key = self._compute_QKV(op, input, key_weight, "key_mm_reshaped")
|
|
90
|
+
value = self._compute_QKV(op, input, value_weight, "value_mm_reshaped")
|
|
91
|
+
|
|
92
|
+
query_rope = op.RotaryEmbedding(query, position_ids, cos, sin, _domain="com.microsoft")
|
|
93
|
+
|
|
94
|
+
key_rope = op.RotaryEmbedding(key, position_ids, cos, sin, _domain="com.microsoft")
|
|
95
|
+
key_rope = op.Concat(past_key, key_rope, axis=-2)
|
|
96
|
+
# Transpose last two axes of key_rope to compute dot-product via matmul.
|
|
97
|
+
key_reshaped = op.Reshape(
|
|
98
|
+
key_rope, _allow_other_inputs=True, _outputs=["key_reshaped"]
|
|
99
|
+
)
|
|
100
|
+
key_reshaped_transposed = op.Transpose(key_reshaped, perm=[0, 2, 1])
|
|
101
|
+
key_transposed = op.Reshape(
|
|
102
|
+
key_reshaped_transposed, _allow_other_inputs=True, _outputs=["key_transposed"]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
value = op.Concat(past_value, value, axis=-2)
|
|
106
|
+
|
|
107
|
+
attention = op.SDPA(
|
|
108
|
+
query_rope, key_transposed, value, mask, _domain="ai.onnxruntime.fusion"
|
|
109
|
+
)
|
|
110
|
+
# Transpose back to (B, S, H, D/H)
|
|
111
|
+
attention_transposed = op.Transpose(attention, perm=[0, 2, 1, 3])
|
|
112
|
+
# Reshape back to (B, S, D)
|
|
113
|
+
attention_reshaped = op.Reshape(
|
|
114
|
+
attention_transposed, _allow_other_inputs=True, _outputs=["attention_reshaped"]
|
|
115
|
+
)
|
|
116
|
+
return attention_reshaped, key_rope, value
|
|
117
|
+
|
|
118
|
+
def check(
|
|
119
|
+
self,
|
|
120
|
+
op,
|
|
121
|
+
query_mm_reshaped,
|
|
122
|
+
key_mm_reshaped,
|
|
123
|
+
value_mm_reshaped,
|
|
124
|
+
key_reshaped,
|
|
125
|
+
key_transposed,
|
|
126
|
+
attention_reshaped,
|
|
127
|
+
**_,
|
|
128
|
+
):
|
|
129
|
+
bindings: dict[str, int] = {}
|
|
130
|
+
status = (
|
|
131
|
+
_check_shape(bindings, query_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
132
|
+
and _check_shape(bindings, key_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
133
|
+
and _check_shape(bindings, value_mm_reshaped, ["B", "S", "H", "d_h"])
|
|
134
|
+
and _check_shape(bindings, key_reshaped, ["B*H", "KVS", "d_h"])
|
|
135
|
+
and _check_shape(bindings, key_transposed, ["B", "H", "d_h", "KVS"])
|
|
136
|
+
and _check_shape(bindings, attention_reshaped, ["B", "S", "H*d_h"])
|
|
137
|
+
)
|
|
138
|
+
if not status:
|
|
139
|
+
return False
|
|
140
|
+
# if bindings["B"] * bindings["H"] != bindings["B*H"]:
|
|
141
|
+
# return False
|
|
142
|
+
# if bindings["H"] * bindings["d_h"] != bindings["H*d_h"]:
|
|
143
|
+
# return False
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
def rewrite(
|
|
147
|
+
self,
|
|
148
|
+
op,
|
|
149
|
+
input,
|
|
150
|
+
query_weight,
|
|
151
|
+
key_weight,
|
|
152
|
+
value_weight,
|
|
153
|
+
mask,
|
|
154
|
+
cos,
|
|
155
|
+
sin,
|
|
156
|
+
past_key,
|
|
157
|
+
past_value,
|
|
158
|
+
position_ids,
|
|
159
|
+
query_mm_reshaped,
|
|
160
|
+
**_,
|
|
161
|
+
):
|
|
162
|
+
num_heads = query_mm_reshaped.shape[2]
|
|
163
|
+
query = op.MatMul(input, query_weight)
|
|
164
|
+
key = op.MatMul(input, key_weight)
|
|
165
|
+
value = op.MatMul(input, value_weight)
|
|
166
|
+
|
|
167
|
+
query_rope = op.RotaryEmbedding(query, position_ids, cos, sin, _domain="com.microsoft")
|
|
168
|
+
key_rope = op.RotaryEmbedding(key, position_ids, cos, sin, _domain="com.microsoft")
|
|
169
|
+
|
|
170
|
+
return op.MultiHeadAttention(
|
|
171
|
+
query_rope,
|
|
172
|
+
key_rope,
|
|
173
|
+
value,
|
|
174
|
+
None, # bias
|
|
175
|
+
None, # key padding mask
|
|
176
|
+
mask, # attention mask/bias
|
|
177
|
+
past_key,
|
|
178
|
+
past_value,
|
|
179
|
+
num_heads=num_heads,
|
|
180
|
+
_domain="com.microsoft",
|
|
181
|
+
_outputs=3,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
_rule1 = MultiHeadAttention.rule("MHA_2dmm", use_2d_matmul=False)
|
|
186
|
+
|
|
187
|
+
mha_rules = pattern.RewriteRuleSet([_rule1])
|
|
188
|
+
|
|
189
|
+
debug: bool = True
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def fuse_mha(model: ir.Model) -> int:
|
|
193
|
+
count = mha_rules.apply_to_model(model)
|
|
194
|
+
if count == 0 and debug:
|
|
195
|
+
mha_rules.apply_to_model(model, debug=True)
|
|
196
|
+
else:
|
|
197
|
+
print(f"MHA count: {count}")
|
|
198
|
+
return count
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import onnxscript.ir as ir
|
|
6
|
+
from onnxscript.rewriter import _ir_utils, pattern
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
RMS Normalization: This is referred to as SimplifiedLayerNormalization in the ORT codebase.
|
|
10
|
+
See https://github.com/microsoft/onnxruntime/blob/6d9636f07cccdb6e4ac453087ad54c3bc9854d50/onnxruntime/core/graph/contrib_ops/contrib_defs.cc#L2981
|
|
11
|
+
|
|
12
|
+
Key points for the fusion optimization:
|
|
13
|
+
* Input and scale are allowed to be of different types.
|
|
14
|
+
* The normalization of the input can be done in a different precision than the input type,
|
|
15
|
+
which is also the precision of reciprocal_rms returned by operation.
|
|
16
|
+
* Input (x) must be: float or double or float16 or bfloat16
|
|
17
|
+
* Scale must be: float or double or float16 or bfloat16
|
|
18
|
+
* Normalization precision must be float or double
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
float_types = [
|
|
22
|
+
ir.DataType.FLOAT,
|
|
23
|
+
ir.DataType.FLOAT16,
|
|
24
|
+
ir.DataType.BFLOAT16,
|
|
25
|
+
ir.DataType.DOUBLE,
|
|
26
|
+
]
|
|
27
|
+
fp_float_types = [ir.DataType.FLOAT, ir.DataType.DOUBLE]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RmsNormFusion(pattern.RewriteRuleClassBase):
|
|
31
|
+
def __init__(self, name: str, *, cast_input: bool, cast_normalized: bool):
|
|
32
|
+
"""
|
|
33
|
+
Args:
|
|
34
|
+
name: Name of the rule.
|
|
35
|
+
cast_input: Whether to cast input to do the normalization in a different precision.
|
|
36
|
+
cast_normalized: Whether to cast the normalized output to the target dtype (same as scale).
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(name=name)
|
|
39
|
+
self._cast_input = cast_input
|
|
40
|
+
self._cast_normalized = cast_normalized
|
|
41
|
+
|
|
42
|
+
def pattern(self, op, x, scale, epsilon, compute_dtype, target_dtype):
|
|
43
|
+
if self._cast_input:
|
|
44
|
+
x = op.Cast(x, to=compute_dtype)
|
|
45
|
+
x_square = op.Pow(x, 2.0)
|
|
46
|
+
mean_square = op.ReduceMean(x_square, [-1], keepdims=1, noop_with_empty_axes=0)
|
|
47
|
+
mean_square_plus_epsilon = op.Add(mean_square, epsilon)
|
|
48
|
+
rms = op.Sqrt(mean_square_plus_epsilon)
|
|
49
|
+
reciprocal_rms = op.Reciprocal(rms)
|
|
50
|
+
normalized = op.Mul(x, reciprocal_rms)
|
|
51
|
+
if self._cast_normalized:
|
|
52
|
+
normalized = op.Cast(normalized, to=target_dtype)
|
|
53
|
+
return op.Mul(scale, normalized)
|
|
54
|
+
|
|
55
|
+
def check(self, op, x, scale, epsilon, compute_dtype, target_dtype):
|
|
56
|
+
"""Check if the pattern matches conditions for use of SimplifiedLayerNormalization op."""
|
|
57
|
+
# epsilon must be a scalar
|
|
58
|
+
epsilon_value = _ir_utils.get_singleton_value(epsilon)
|
|
59
|
+
if not isinstance(epsilon_value, float): # TODO: support other types
|
|
60
|
+
return False
|
|
61
|
+
# input and output must be same dtype
|
|
62
|
+
if x.dtype not in float_types:
|
|
63
|
+
return False
|
|
64
|
+
if scale.dtype not in float_types:
|
|
65
|
+
return False
|
|
66
|
+
stash_dtype = compute_dtype.value if self._cast_input else x.dtype
|
|
67
|
+
if stash_dtype not in fp_float_types:
|
|
68
|
+
return False
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
def rewrite(self, op, x, scale, epsilon, compute_dtype, target_dtype):
|
|
72
|
+
stash_dtype = compute_dtype.value if self._cast_input else x.dtype
|
|
73
|
+
# Note: ORT's SimplifiedLayerNormalization was placed in onnx domain by mistake.
|
|
74
|
+
# No need to use com.microsoft domain here.
|
|
75
|
+
return op.SimplifiedLayerNormalization(
|
|
76
|
+
x,
|
|
77
|
+
scale,
|
|
78
|
+
axis=-1,
|
|
79
|
+
epsilon=_ir_utils.get_singleton_value(epsilon),
|
|
80
|
+
stash_type=stash_dtype,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_rule_0 = RmsNormFusion.rule("RmsNorm-0", cast_input=True, cast_normalized=True)
|
|
85
|
+
_rule_1 = RmsNormFusion.rule("RmsNorm-1", cast_input=False, cast_normalized=True)
|
|
86
|
+
_rule_2 = RmsNormFusion.rule("RmsNorm-2", cast_input=True, cast_normalized=False)
|
|
87
|
+
_rule_3 = RmsNormFusion.rule("RmsNorm-3", cast_input=False, cast_normalized=False)
|
|
88
|
+
|
|
89
|
+
rms_normalization_rules = [_rule_0, _rule_1, _rule_2, _rule_3]
|
|
90
|
+
rms_normalization_ruleset = pattern.RewriteRuleSet(rms_normalization_rules)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def fuse_rms_normalization(model: ir.Model) -> None:
|
|
94
|
+
count = rms_normalization_ruleset.apply_to_model(model)
|
|
95
|
+
print(f"RMS Normalization count: {count}")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import onnxscript.ir as ir
|
|
6
|
+
from onnxscript.rewriter import _ir_utils, pattern
|
|
7
|
+
|
|
8
|
+
# Add first version of the RotaryEmbeddingFusion rule. This considers only one simple pattern
|
|
9
|
+
# for full rotation without interleaving.
|
|
10
|
+
# TODO(rama): Add pattern variations to handle other cases (interleaved, as well as partial rotation).
|
|
11
|
+
|
|
12
|
+
# Note: This targets the new op being proposed to ONNX. This version does not exist in ORT yet.
|
|
13
|
+
# so it can't be tested by running against ORT. See cos_sin_cache.py for a transformation that
|
|
14
|
+
# rewrites the pattern into one that can be run against ORT.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _rotate_half_pattern(op, x, start1, end1, start2, end2):
|
|
18
|
+
# Slice(input, starts, ends, axes, steps)
|
|
19
|
+
x1 = op.Slice(x, start1, end1, [3], [1])
|
|
20
|
+
x2 = op.Slice(x, start2, end2, [3], [1])
|
|
21
|
+
minus_x2 = op.Neg(x2)
|
|
22
|
+
rotated_x = op.Concat(minus_x2, x1, axis=-1)
|
|
23
|
+
return rotated_x
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RotaryEmbeddingFusion(pattern.RewriteRuleClassBase):
|
|
27
|
+
def pattern(self, op, x, cos, sin, start1, end1, start2, end2):
|
|
28
|
+
return x * cos + _rotate_half_pattern(op, x, start1, end1, start2, end2) * sin
|
|
29
|
+
|
|
30
|
+
def check(self, op, x, start1, end1, start2, end2, **_):
|
|
31
|
+
# x needs to be a 4D tensor with known last dimension size (== head_size) and known second dimension (num_heads)
|
|
32
|
+
if x is None or x.shape is None or len(x.shape) != 4:
|
|
33
|
+
return False
|
|
34
|
+
if not isinstance(x.shape[1], int):
|
|
35
|
+
return False
|
|
36
|
+
head_size = x.shape[3]
|
|
37
|
+
if not isinstance(head_size, int):
|
|
38
|
+
return False
|
|
39
|
+
half_head_size = head_size // 2
|
|
40
|
+
|
|
41
|
+
# Check that x is being split into two equal halves of size half_head_size
|
|
42
|
+
return (
|
|
43
|
+
_ir_utils.is_singleton_value(start1, 0)
|
|
44
|
+
and _ir_utils.is_singleton_value(end1, half_head_size)
|
|
45
|
+
and _ir_utils.is_singleton_value(start2, half_head_size)
|
|
46
|
+
and _ir_utils.is_singleton_value(end2, lambda x: x >= head_size)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def rewrite(self, op, x, cos, sin, **_):
|
|
50
|
+
num_heads = x.shape[1]
|
|
51
|
+
return op.RotaryEmbedding(
|
|
52
|
+
x, cos, sin, interleaved=0, num_heads=num_heads, _domain="ai.onnxruntime.fusion"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_rule = RotaryEmbeddingFusion.rule()
|
|
57
|
+
|
|
58
|
+
rotary_embedding_rules = pattern.RewriteRuleSet([_rule])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def fuse_rotary_embedding(model: ir.Model) -> int:
|
|
62
|
+
count = rotary_embedding_rules.apply_to_model(model)
|
|
63
|
+
print(f"Rotary Embedding count: {count}")
|
|
64
|
+
return count
|