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.
Files changed (176) hide show
  1. onnxscript/__init__.py +131 -0
  2. onnxscript/_framework_apis/__init__.py +3 -0
  3. onnxscript/_framework_apis/torch_2_5.py +117 -0
  4. onnxscript/_framework_apis/torch_2_6.py +45 -0
  5. onnxscript/_internal/__init__.py +0 -0
  6. onnxscript/_internal/analysis.py +229 -0
  7. onnxscript/_internal/ast_utils.py +64 -0
  8. onnxscript/_internal/autocast.py +250 -0
  9. onnxscript/_internal/deprecation.py +78 -0
  10. onnxscript/_internal/param_manipulation.py +148 -0
  11. onnxscript/_internal/runtime_typing.py +43 -0
  12. onnxscript/_internal/utils.py +99 -0
  13. onnxscript/_internal/version_utils.py +118 -0
  14. onnxscript/_legacy_ir/__init__.py +341 -0
  15. onnxscript/_legacy_ir/visitor.py +937 -0
  16. onnxscript/_thirdparty/asciichartpy.py +313 -0
  17. onnxscript/backend/__init__.py +2 -0
  18. onnxscript/backend/onnx_backend.py +303 -0
  19. onnxscript/backend/onnx_export.py +885 -0
  20. onnxscript/converter.py +1470 -0
  21. onnxscript/evaluator.py +619 -0
  22. onnxscript/function_libs/tools/torch_lib/deduce_type_constraints.py +403 -0
  23. onnxscript/function_libs/tools/torch_lib/generate_aten_signatures.py +333 -0
  24. onnxscript/function_libs/tools/torch_lib/generate_prims_signatures.py +331 -0
  25. onnxscript/function_libs/torch_lib/__init__.py +12 -0
  26. onnxscript/function_libs/torch_lib/_constants.py +5 -0
  27. onnxscript/function_libs/torch_lib/_flags.py +58 -0
  28. onnxscript/function_libs/torch_lib/graph_building/__init__.py +56 -0
  29. onnxscript/function_libs/torch_lib/graph_building/_graph_building_ir.py +723 -0
  30. onnxscript/function_libs/torch_lib/graph_building/_graph_building_torch.py +1125 -0
  31. onnxscript/function_libs/torch_lib/ops/__init__.py +27 -0
  32. onnxscript/function_libs/torch_lib/ops/common.py +80 -0
  33. onnxscript/function_libs/torch_lib/ops/core.py +8935 -0
  34. onnxscript/function_libs/torch_lib/ops/fft.py +385 -0
  35. onnxscript/function_libs/torch_lib/ops/linalg.py +399 -0
  36. onnxscript/function_libs/torch_lib/ops/nested.py +25 -0
  37. onnxscript/function_libs/torch_lib/ops/nn.py +2713 -0
  38. onnxscript/function_libs/torch_lib/ops/prims.py +850 -0
  39. onnxscript/function_libs/torch_lib/ops/quantized_decomposed.py +63 -0
  40. onnxscript/function_libs/torch_lib/ops/sparse.py +23 -0
  41. onnxscript/function_libs/torch_lib/ops/special.py +387 -0
  42. onnxscript/function_libs/torch_lib/ops/vision.py +25 -0
  43. onnxscript/function_libs/torch_lib/registration.py +151 -0
  44. onnxscript/function_libs/torch_lib/tensor_typing.py +74 -0
  45. onnxscript/ir/__init__.py +153 -0
  46. onnxscript/ir/_convenience.py +447 -0
  47. onnxscript/ir/_core.py +3119 -0
  48. onnxscript/ir/_display.py +49 -0
  49. onnxscript/ir/_enums.py +163 -0
  50. onnxscript/ir/_graph_comparison.py +23 -0
  51. onnxscript/ir/_io.py +97 -0
  52. onnxscript/ir/_linked_list.py +276 -0
  53. onnxscript/ir/_metadata.py +44 -0
  54. onnxscript/ir/_name_authority.py +72 -0
  55. onnxscript/ir/_polyfill.py +25 -0
  56. onnxscript/ir/_protocols.py +598 -0
  57. onnxscript/ir/_schemas.py +548 -0
  58. onnxscript/ir/_tape.py +120 -0
  59. onnxscript/ir/_type_casting.py +106 -0
  60. onnxscript/ir/convenience.py +32 -0
  61. onnxscript/ir/external_data.py +396 -0
  62. onnxscript/ir/passes/__init__.py +33 -0
  63. onnxscript/ir/passes/_pass_infra.py +172 -0
  64. onnxscript/ir/serde.py +1620 -0
  65. onnxscript/ir/tensor_adapters.py +122 -0
  66. onnxscript/ir/traversal.py +82 -0
  67. onnxscript/irbuilder.py +542 -0
  68. onnxscript/main.py +167 -0
  69. onnxscript/onnx_opset/__init__.py +232 -0
  70. onnxscript/onnx_opset/_impl/opset1.py +4100 -0
  71. onnxscript/onnx_opset/_impl/opset10.py +1227 -0
  72. onnxscript/onnx_opset/_impl/opset11.py +4013 -0
  73. onnxscript/onnx_opset/_impl/opset12.py +1078 -0
  74. onnxscript/onnx_opset/_impl/opset13.py +3924 -0
  75. onnxscript/onnx_opset/_impl/opset14.py +999 -0
  76. onnxscript/onnx_opset/_impl/opset15.py +604 -0
  77. onnxscript/onnx_opset/_impl/opset16.py +1255 -0
  78. onnxscript/onnx_opset/_impl/opset17.py +561 -0
  79. onnxscript/onnx_opset/_impl/opset18.py +1803 -0
  80. onnxscript/onnx_opset/_impl/opset19.py +1942 -0
  81. onnxscript/onnx_opset/_impl/opset2.py +218 -0
  82. onnxscript/onnx_opset/_impl/opset20.py +675 -0
  83. onnxscript/onnx_opset/_impl/opset21.py +1976 -0
  84. onnxscript/onnx_opset/_impl/opset22.py +2588 -0
  85. onnxscript/onnx_opset/_impl/opset3.py +199 -0
  86. onnxscript/onnx_opset/_impl/opset4.py +77 -0
  87. onnxscript/onnx_opset/_impl/opset5.py +84 -0
  88. onnxscript/onnx_opset/_impl/opset6.py +944 -0
  89. onnxscript/onnx_opset/_impl/opset7.py +1243 -0
  90. onnxscript/onnx_opset/_impl/opset8.py +444 -0
  91. onnxscript/onnx_opset/_impl/opset9.py +1485 -0
  92. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml1.py +974 -0
  93. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml2.py +112 -0
  94. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml3.py +308 -0
  95. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml4.py +129 -0
  96. onnxscript/onnx_opset/_impl/opset_ai_onnx_ml5.py +158 -0
  97. onnxscript/onnx_opset/_impl/opset_ai_onnx_preview_training1.py +577 -0
  98. onnxscript/onnx_types.py +229 -0
  99. onnxscript/optimizer/__init__.py +39 -0
  100. onnxscript/optimizer/_constant_folding.py +1083 -0
  101. onnxscript/optimizer/_inliner.py +312 -0
  102. onnxscript/optimizer/_legacy/_optimizer.py +98 -0
  103. onnxscript/optimizer/_legacy/_remove_unused_proto.py +144 -0
  104. onnxscript/optimizer/_legacy/_simple_function_folding.py +243 -0
  105. onnxscript/optimizer/_legacy/constant_folding.py +293 -0
  106. onnxscript/optimizer/_legacy/evaluator.py +439 -0
  107. onnxscript/optimizer/_optimizer.py +61 -0
  108. onnxscript/optimizer/_remove_unused.py +106 -0
  109. onnxscript/optimizer/_remove_unused_function.py +72 -0
  110. onnxscript/py.typed +1 -0
  111. onnxscript/rewriter/__init__.py +56 -0
  112. onnxscript/rewriter/_ir_utils.py +111 -0
  113. onnxscript/rewriter/broadcast_to_matmul.py +180 -0
  114. onnxscript/rewriter/cast_constant_of_shape.py +50 -0
  115. onnxscript/rewriter/collapse_slices.py +141 -0
  116. onnxscript/rewriter/erfgelu.py +27 -0
  117. onnxscript/rewriter/function_rule.py +232 -0
  118. onnxscript/rewriter/gemm_to_matmul_add.py +21 -0
  119. onnxscript/rewriter/generic_pattern.py +700 -0
  120. onnxscript/rewriter/llama_rule_sets.py +287 -0
  121. onnxscript/rewriter/no_op.py +56 -0
  122. onnxscript/rewriter/onnxruntime/__init__.py +50 -0
  123. onnxscript/rewriter/onnxruntime/bfloat16_utils/bfloat16_converter.py +99 -0
  124. onnxscript/rewriter/onnxruntime/fused_matmul_rule_sets.py +177 -0
  125. onnxscript/rewriter/onnxruntime/group_normalization_merge_silu.py +64 -0
  126. onnxscript/rewriter/onnxruntime/instance_to_group_normalization.py +155 -0
  127. onnxscript/rewriter/onnxruntime/softmax.py +63 -0
  128. onnxscript/rewriter/onnxruntime/transformers/__init__.py +21 -0
  129. onnxscript/rewriter/onnxruntime/transformers/biassplitgelu.py +31 -0
  130. onnxscript/rewriter/onnxruntime/transformers/fastgelu.py +29 -0
  131. onnxscript/rewriter/onnxruntime/transformers/layernorm.py +47 -0
  132. onnxscript/rewriter/onnxruntime/transformers/multihead_attention.py +715 -0
  133. onnxscript/rewriter/ort_fusions/__init__.py +9 -0
  134. onnxscript/rewriter/ort_fusions/_core.py +28 -0
  135. onnxscript/rewriter/ort_fusions/_smollm_1.py +253 -0
  136. onnxscript/rewriter/ort_fusions/_smollm_2.py +467 -0
  137. onnxscript/rewriter/ort_fusions/_test_models.py +122 -0
  138. onnxscript/rewriter/ort_fusions/_test_utils.py +42 -0
  139. onnxscript/rewriter/ort_fusions/cos_sin_cache.py +154 -0
  140. onnxscript/rewriter/ort_fusions/gqa.py +156 -0
  141. onnxscript/rewriter/ort_fusions/mha.py +198 -0
  142. onnxscript/rewriter/ort_fusions/rms_normalization.py +95 -0
  143. onnxscript/rewriter/ort_fusions/rotary_embedding.py +64 -0
  144. onnxscript/rewriter/ort_fusions/sdpa.py +75 -0
  145. onnxscript/rewriter/ort_fusions/skip_normalization.py +46 -0
  146. onnxscript/rewriter/pattern.py +1714 -0
  147. onnxscript/rewriter/testing.py +77 -0
  148. onnxscript/sourceinfo.py +59 -0
  149. onnxscript/tensor.py +227 -0
  150. onnxscript/testing/__init__.py +482 -0
  151. onnxscript/tools/__init__.py +4 -0
  152. onnxscript/tools/benchmark/__init__.py +23 -0
  153. onnxscript/tools/benchmark/benchmark_helpers.py +783 -0
  154. onnxscript/tools/benchmark/benchmark_run.py +140 -0
  155. onnxscript/tools/benchmark/export_model.py +207 -0
  156. onnxscript/tools/benchmark/export_model_batch.py +146 -0
  157. onnxscript/tools/memory_peak.py +244 -0
  158. onnxscript/tools/training_helper.py +50 -0
  159. onnxscript/tools/transformers_models/__init__.py +190 -0
  160. onnxscript/tools/transformers_models/llama.py +168 -0
  161. onnxscript/tools/transformers_models/mistral.py +238 -0
  162. onnxscript/tools/transformers_models/phi.py +248 -0
  163. onnxscript/tools/transformers_models/phi3.py +259 -0
  164. onnxscript/type_annotation.py +281 -0
  165. onnxscript/utils/__init__.py +0 -0
  166. onnxscript/utils/evaluation_utils.py +56 -0
  167. onnxscript/utils/timing_utils.py +33 -0
  168. onnxscript/utils/utils.py +84 -0
  169. onnxscript/values.py +790 -0
  170. onnxscript/version_converter/__init__.py +21 -0
  171. onnxscript/version_converter/_version_converter.py +314 -0
  172. onnxscript-0.1.0.dist-info/LICENSE +21 -0
  173. onnxscript-0.1.0.dist-info/METADATA +370 -0
  174. onnxscript-0.1.0.dist-info/RECORD +176 -0
  175. onnxscript-0.1.0.dist-info/WHEEL +5 -0
  176. 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