Coverage for mlprodict/onnxrt/ops_empty/_op.py: 71%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

86 statements  

1# -*- encoding: utf-8 -*- 

2""" 

3@file 

4@brief Shortcut to *ops_onnxruntime*. 

5""" 

6import numpy 

7import onnx.defs 

8from onnx.helper import make_tensor 

9import skl2onnx.algebra.onnx_ops as alg 

10try: 

11 import skl2onnx.algebra.custom_ops as alg2 

12except ImportError: # pragma: no cover 

13 # older version of skl2onnx 

14 alg2 = alg 

15from ...onnx_tools.onnx2py_helper import guess_proto_dtype 

16from ...onnx_tools.optim.graph_schema_helper import ( 

17 get_defined_inputs, get_defined_outputs, proto2vars) 

18 

19 

20_schemas = { 

21 schema.name: schema for schema in onnx.defs.get_all_schemas_with_history()} 

22 

23 

24class OpRunOnnxEmpty: 

25 """ 

26 Unique operator for an empty runtime. 

27 """ 

28 

29 def __init__(self, onnx_node, desc=None, variables=None, 

30 dtype=None, **options): 

31 """ 

32 :param onnx_node: :epkg:`onnx` node 

33 :param desc: internal representation 

34 :param variables: registered variables created by previous operators 

35 :param dtype: float computation type 

36 :param options: runtime options 

37 """ 

38 self._provider = 'empty' 

39 self.onnx_node = onnx_node 

40 self.desc = desc 

41 self._schema = _schemas.get(onnx_node.op_type, None) 

42 if desc is not None: 

43 if 'atts' in desc: 

44 for a, b in desc['atts'].items(): 

45 if not isinstance(b, dict) or 'value' not in b: 

46 raise ValueError( # pragma: no cover 

47 "Unexpected value {}.".format(b)) 

48 options[a] = b['value'] 

49 

50 self.options = options 

51 self.dtype = dtype 

52 self._init(variables) 

53 

54 def _name_mapping(self, inputs): 

55 mapping = {} 

56 new_inputs = [] 

57 for name in inputs: 

58 if name in mapping: 

59 i = 0 

60 new_name = "{}_{}".format(name, i) 

61 while new_name in mapping: 

62 i += 1 # pragma: no cover 

63 new_name = "{}_{}".format(name, i) # pragma: no cover 

64 mapping[new_name] = name 

65 new_inputs.append(new_name) 

66 else: 

67 new_inputs.append(name) 

68 mapping[name] = name 

69 return mapping, new_inputs 

70 

71 def _guess_proto_type(self, dtype): 

72 return guess_proto_dtype(dtype) 

73 

74 def _init(self, variables=None): 

75 """ 

76 Initializes the node. 

77 

78 @param variables registered variables created by previous operators 

79 

80 The current implementation for operator *Scan* 

81 only works for matrices. 

82 """ 

83 try: 

84 self.alg_class = getattr(alg2, 'Onnx' + self.onnx_node.op_type) 

85 except AttributeError: 

86 try: 

87 self.alg_class = getattr(alg, 'Onnx' + self.onnx_node.op_type) 

88 except AttributeError: 

89 self.alg_class = None 

90 inputs = list(self.onnx_node.input) 

91 self.mapping, self.inputs = self._name_mapping(inputs) 

92 self.outputs = list(self.onnx_node.output) 

93 

94 options = self.options.copy() 

95 target_opset = options.pop('target_opset', None) 

96 domain = options.pop('domain', None) 

97 # disable_optimisation = options.pop('disable_optimisation', False) 

98 # ir_version = options.pop('ir_version', None) 

99 

100 if self.alg_class is None: 

101 self.onnx_ = self.onnx_node 

102 elif self.onnx_node.op_type == 'ConstantOfShape': 

103 for k in options: 

104 v = options[k] 

105 if isinstance(v, numpy.ndarray): 

106 options[k] = make_tensor( 

107 k, self._guess_proto_type(v.dtype), 

108 v.shape, v.tolist()) 

109 

110 self.inst_ = self.alg_class(*self.inputs, output_names=self.outputs, 

111 op_version=target_opset, **options) 

112 inputs = get_defined_inputs( 

113 self.inputs, variables, dtype=self.dtype) 

114 try: 

115 self.onnx_ = self.inst_.to_onnx(inputs, target_opset=target_opset, 

116 domain=domain) 

117 if "dim_value: 0" in str(self.onnx_): 

118 raise RuntimeError( # pragma: no cover 

119 "Probable issue as one dimension is null.\n--\n{}".format( 

120 self.onnx_)) 

121 except AttributeError as e: # pragma: no cover 

122 # older version of skl2onnx 

123 self.onnx_ = self.inst_.to_onnx(inputs) 

124 if "dim_value: 0" in str(self.onnx_): 

125 raise RuntimeError( 

126 "Probable issue as one dimension is null.\n--\n{}".format( 

127 self.onnx_)) from e 

128 elif self.onnx_node.op_type == 'Scan': 

129 self.inst_ = self.alg_class( 

130 *self.inputs, output_names=self.outputs, 

131 op_version=target_opset, **options) 

132 inputs = get_defined_inputs( 

133 self.inputs, variables, dtype=self.dtype) 

134 outputs = get_defined_outputs( 

135 self.outputs, self.onnx_node, inputs, variables, 

136 dtype=self.dtype) 

137 inputs = [(name, cl.__class__([None, None])) 

138 for (name, cl) in inputs] 

139 outputs = [(name, cl.__class__([None, None])) 

140 for (name, cl) in outputs] 

141 self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs, 

142 target_opset=target_opset, 

143 domain=domain) 

144 if "dim_value: 0" in str(self.onnx_): 

145 raise RuntimeError( # pragma: no cover 

146 "Probable issue as one dimension is null.\n--\n{}".format( 

147 self.onnx_)) 

148 else: 

149 self.inst_ = self.alg_class(*self.inputs, output_names=self.outputs, 

150 op_version=target_opset, domain=domain, 

151 **options) 

152 inputs = get_defined_inputs( 

153 self.inputs, variables, dtype=self.dtype) 

154 

155 try: 

156 self.onnx_ = self.inst_.to_onnx( 

157 inputs, target_opset=target_opset, domain=domain) 

158 if "dim_value: 0" in str(self.onnx_): 

159 raise RuntimeError( # pragma: no cover 

160 "Probable issue as one dimension is null.\n--\n{}\n---\n{}".format( 

161 self.onnx_, inputs)) 

162 except (RuntimeError, ValueError): # pragma: no cover 

163 # Let's try again by forcing output types. 

164 outputs = get_defined_outputs( 

165 self.outputs, self.onnx_node, inputs, variables, 

166 dtype=self.dtype) 

167 self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs, 

168 target_opset=target_opset, 

169 domain=domain) 

170 if "dim_value: 0" in str(self.onnx_): 

171 raise RuntimeError( # pragma: no cover 

172 "Probable issue as one dimension is null.\n--\n{}".format( 

173 self.onnx_)) from e 

174 

175 if hasattr(self.onnx_, 'graph'): 

176 if len(self.onnx_.graph.output) != len(self.outputs): # pragma: no cover 

177 # Something is wrong, falls back to default plan. 

178 outputs = get_defined_outputs( 

179 self.outputs, self.onnx_node, inputs, variables, 

180 dtype=self.dtype) 

181 self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs, 

182 target_opset=target_opset, 

183 domain=domain) 

184 if "dim_value: 0" in str(self.onnx_): 

185 raise RuntimeError( # pragma: no cover 

186 "Probable issue as one dimension is null.\n--\n{}".format( 

187 self.onnx_)) 

188 else: 

189 lo = list(self.onnx_.graph.output) 

190 outputs = proto2vars(lo) 

191 else: 

192 outputs = [(o, None) for o in self.onnx_.output] 

193 

194 self.typed_outputs_ = outputs 

195 

196 def run(self, *args, **kwargs): 

197 """ 

198 Should be overwritten. 

199 """ 

200 # inputs = {name: val for name, val in zip(self.inputs, args)} 

201 raise RuntimeError( # pragma: no cover 

202 "This runtime does nothing. Running it is useless.") 

203 

204 def need_context(self): 

205 """ 

206 Tells the runtime if this node needs the context 

207 (all the results produced so far) as it may silently access 

208 one of them (operator Loop). 

209 The default answer is `False`. 

210 """ 

211 return False