Coverage for /opt/cloud/slavespace/usr1/096471637100f3de0fcfc01072822a80/dttest/api/python/ge/ge/passes/pattern.py: 89%

140 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-27 10:02 +0800

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3# ----------------------------------------------------------------------------------------------------------- 

4# Copyright (c) 2026 Huawei Technologies Co., Ltd. 

5# This program is free software, you can redistribute it and/or modify it under the terms and conditions of 

6# CANN Open Software License Agreement Version 2.0 (the "License"). 

7# Please refer to the License for details. You may not use this file except in compliance with the License. 

8# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, 

9# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. 

10# See LICENSE in the root of the software repository for the full text of the License. 

11# ----------------------------------------------------------------------------------------------------------- 

12 

13"""PatternFusionPass helper types and constructors.""" 

14 

15from __future__ import annotations 

16 

17import inspect 

18from dataclasses import dataclass 

19from functools import wraps 

20from typing import TYPE_CHECKING, Callable, Dict, Iterator, List, Type, Union, overload 

21 

22from ge.es.graph_builder import GraphBuilder 

23from ge.es.tensor_holder import TensorHolder 

24from ge.graph import Graph, Node 

25 

26if TYPE_CHECKING: 

27 from ._native import Pattern 

28 

29 

30_PATTERN_METHOD_MARK = "__ge_expression_pattern_method__" 

31 

32 

33def __getattr__(name: str): 

34 if name == "Pattern": 

35 from ._native import Pattern 

36 

37 globals()["Pattern"] = Pattern 

38 return Pattern 

39 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

40 

41 

42@dataclass(frozen=True) 

43class NodeIo: 

44 """Python helper describing one node output.""" 

45 

46 node: Node 

47 index: int = 0 

48 

49 

50class PatternInputs: 

51 """Lazy graph input collection passed to expression-style pass hooks. 

52 

53 Users receive this object from ``@pattern`` methods or 

54 ``replacement(self, inputs)``. Accessing ``inputs[i]`` creates graph input 

55 ``i`` on demand. Use ``inputs[:N]`` to declare multiple inputs explicitly. 

56 The input count is intentionally unknown, so direct iteration is rejected. 

57 """ 

58 

59 def __init__(self, builder: GraphBuilder) -> None: 

60 self._builder = builder 

61 self._inputs: Dict[int, TensorHolder] = {} 

62 

63 @overload 

64 def __getitem__(self, index: int) -> TensorHolder: ... 

65 

66 @overload 

67 def __getitem__(self, index: slice) -> List[TensorHolder]: ... 

68 

69 def __getitem__(self, index: Union[int, slice]) -> Union[TensorHolder, List[TensorHolder]]: 

70 if isinstance(index, slice): 

71 return self._get_slice(index) 

72 if not isinstance(index, int): 

73 raise TypeError("PatternInputs index must be an integer or slice") 

74 if index < 0: 

75 raise ValueError("PatternInputs index must be non-negative") 

76 self._ensure_created(index) 

77 return self._inputs[index] 

78 

79 def __iter__(self) -> Iterator[TensorHolder]: 

80 raise TypeError("PatternInputs cannot be iterated because the input count is unknown; use inputs[:N]") 

81 

82 def _get_slice(self, index_slice: slice) -> List[TensorHolder]: 

83 if index_slice.step not in (None, 1): 

84 raise ValueError("PatternInputs slice step must be 1") 

85 start = 0 if index_slice.start is None else index_slice.start 

86 stop = index_slice.stop 

87 if stop is None: 

88 raise ValueError("PatternInputs slice must provide a stop index, for example inputs[:3]") 

89 if start < 0 or stop < 0: 

90 raise ValueError("PatternInputs slice indices must be non-negative") 

91 if stop < start: 

92 raise ValueError("PatternInputs slice stop must be greater than or equal to start") 

93 return [self[i] for i in range(start, stop)] 

94 

95 def _ensure_created(self, index: int) -> None: 

96 for input_index in range(len(self._inputs), index + 1): 

97 self._inputs[input_index] = self._builder.create_input(input_index) 

98 

99 def created(self) -> List[TensorHolder]: 

100 """Return created inputs in graph input index order.""" 

101 

102 return [self._inputs[index] for index in sorted(self._inputs)] 

103 

104 

105def create_pattern(graph: Graph) -> "Pattern": 

106 """Build a native Pattern from a pattern graph.""" 

107 from ._native import Pattern 

108 

109 return Pattern(graph) 

110 

111 

112def ensure_pattern(pattern_or_graph: Union["Pattern", Graph]) -> "Pattern": 

113 """Convert a Graph into Pattern on demand for bridge helpers.""" 

114 from ._native import Pattern 

115 

116 if isinstance(pattern_or_graph, Graph): 

117 return create_pattern(pattern_or_graph) 

118 if isinstance(pattern_or_graph, Pattern): 

119 return pattern_or_graph 

120 raise TypeError("PatternFusionPass.patterns must return Pattern or Graph objects") 

121 

122 

123def pattern(method: Callable[..., object]) -> Callable[..., object]: 

124 """Mark a ``PatternFusionPass`` method as one expression-style pattern.""" 

125 

126 setattr(method, _PATTERN_METHOD_MARK, True) 

127 return method 

128 

129 

130def _has_decorated_pattern_methods(cls: Type[object]) -> bool: 

131 return bool(_get_decorated_pattern_methods(cls)) 

132 

133 

134def _adapt_decorated_pattern_methods(cls: Type[object]) -> Callable[..., object]: 

135 """Build legacy ``patterns(self)`` from ``@pattern`` expression methods.""" 

136 

137 methods = _get_decorated_pattern_methods(cls) 

138 for method in methods: 

139 _check_required_arg_count( 

140 method, 

141 min_count=2, 

142 max_count=2, 

143 message="@pattern methods only support method(self, inputs)", 

144 ) 

145 

146 def wrapper(self) -> List["Pattern"]: 

147 patterns = [] 

148 for method in methods: 

149 builder = GraphBuilder(_decorated_pattern_graph_name(self, method)) 

150 inputs = PatternInputs(builder) 

151 result = method(self, inputs) 

152 patterns.extend(_build_patterns_from_expression_result(builder, inputs, result)) 

153 return patterns 

154 

155 return wrapper 

156 

157 

158def _adapt_expression_replacement( 

159 method: Callable[..., object], 

160) -> Callable[..., object]: 

161 """Wrap ``replacement(self, inputs[, match_result])`` into the legacy hook.""" 

162 

163 positional_params = _positional_params(method) 

164 if len(positional_params) < 2 or positional_params[1].name != "inputs": 

165 return method 

166 _check_required_arg_count( 

167 method, 

168 min_count=2, 

169 max_count=3, 

170 message="Expression-style replacement only supports " 

171 "replacement(self, inputs) or replacement(self, inputs, match_result)", 

172 ) 

173 accepts_match_result = _positional_arg_count(method) == 3 

174 

175 @wraps(method) 

176 def wrapper(self, match_result: object) -> Graph: 

177 builder = GraphBuilder(_default_graph_name(self, "replacement")) 

178 inputs = PatternInputs(builder) 

179 if accepts_match_result: 

180 result = method(self, inputs, match_result) 

181 else: 

182 result = method(self, inputs) 

183 return _build_replacement_from_expression_result(builder, result) 

184 

185 return wrapper 

186 

187 

188def _check_required_arg_count(method: Callable[..., object], *, min_count: int, max_count: int, message: str) -> None: 

189 count = _positional_arg_count(method) 

190 if count < min_count or count > max_count or _has_required_keyword_only_args(method): 

191 raise TypeError(message) 

192 

193 

194def _positional_arg_count(method: Callable[..., object]) -> int: 

195 return len(_positional_params(method)) 

196 

197 

198def _positional_params(method: Callable[..., object]) -> List[inspect.Parameter]: 

199 signature = inspect.signature(method) 

200 return [ 

201 param 

202 for param in signature.parameters.values() 

203 if param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD) 

204 ] 

205 

206 

207def _has_required_keyword_only_args(method: Callable[..., object]) -> bool: 

208 signature = inspect.signature(method) 

209 return any( 

210 param.kind == inspect.Parameter.KEYWORD_ONLY and param.default is inspect.Parameter.empty 

211 for param in signature.parameters.values() 

212 ) 

213 

214 

215def _get_decorated_pattern_methods(cls: Type[object]) -> List[Callable[..., object]]: 

216 return [ 

217 method for method in cls.__dict__.values() if callable(method) and getattr(method, _PATTERN_METHOD_MARK, False) 

218 ] 

219 

220 

221def _default_graph_name(instance: object, suffix: str) -> str: 

222 return f"{instance.__class__.__name__}_{suffix}" 

223 

224 

225def _decorated_pattern_graph_name(instance: object, method: Callable[..., object]) -> str: 

226 return f"{instance.__class__.__name__}_{method.__name__}_pattern" 

227 

228 

229def _build_patterns_from_expression_result( 

230 builder: GraphBuilder, inputs: PatternInputs, result: object 

231) -> List["Pattern"]: 

232 if _is_pattern_or_graph(result): 

233 return [ensure_pattern(result)] 

234 if isinstance(result, (list, tuple)) and result and all(_is_pattern_or_graph(item) for item in result): 

235 raise TypeError( 

236 "A @pattern method supports a single pattern only. " 

237 "For multiple patterns, declare several @pattern methods or use legacy patterns(self)." 

238 ) 

239 

240 pattern_outputs = _normalize_tensor_outputs(result, "patterns") 

241 graph = builder.build_and_reset(pattern_outputs) 

242 built_pattern = create_pattern(graph) 

243 for input_tensor in inputs.created(): 

244 built_pattern.capture_tensor(input_tensor) 

245 for output_tensor in pattern_outputs: 

246 built_pattern.capture_tensor(output_tensor) 

247 return [built_pattern] 

248 

249 

250def _build_replacement_from_expression_result(builder: GraphBuilder, result: object) -> Graph: 

251 if isinstance(result, Graph): 

252 return result 

253 return builder.build_and_reset(_normalize_tensor_outputs(result, "replacement")) 

254 

255 

256def _normalize_tensor_outputs(result: object, hook_name: str) -> List[TensorHolder]: 

257 if isinstance(result, TensorHolder): 

258 return [result] 

259 if isinstance(result, tuple): 

260 result = list(result) 

261 if isinstance(result, list) and result and all(isinstance(item, TensorHolder) for item in result): 

262 return result 

263 raise TypeError( 

264 f"Expression-style {hook_name} must return a TensorHolder or a non-empty list/tuple of TensorHolder" 

265 ) 

266 

267 

268def _is_pattern_or_graph(result: object) -> bool: 

269 from ._native import Pattern 

270 

271 return isinstance(result, (Pattern, Graph))