model.py Source File

model.py Source File#

SDK qb Runtime Library: model.py Source File
SDK qb Runtime Library v0.29
MCS001-EN
model.py
Go to the documentation of this file.
1
4
5from typing import List, Optional, Tuple, Union
6
7import numpy as np
8
9import maccel.maccel as _cMaccel
10from .accelerator import Accelerator
11from .future import *
12from .model_variant_handle import *
13from .type import *
14
15_Shape = Tuple[int, ...]
16
17__all__ = ["Model", "load"]
18
19
22
23# input ndarray의 shape이 유효한 shape인지 판별한다.
24def _is_valid_shape(input_shape: _Shape, shape: _Shape) -> bool:
25 if (len(input_shape) < len(shape)) or (len(input_shape) > len(shape) + 1):
26 return False
27 # input을 batch일 경우도 고려하여 [h, w, c] 및 [batch, h, w, c] 모두 고려한다
28 offset = 1 if len(input_shape) > len(shape) else 0
29 for s1, s2 in zip(input_shape[offset:], shape):
30 # Dimensions that allow variable lengths are represented by negative values.
31 # A variable-length dimension only permits multiples of the original value.
32 if s1 % s2 != 0 or (s2 > 0 and s1 != s2):
33 return False
34 return True
35
36
37# input ndarray의 shape를 검사하여 HWC인지 CHW인지 판별한다.
38def _is_shape_hwc(inputs: List[np.ndarray], shapes: List[_Shape]) -> Optional[bool]:
39 if len(inputs) != len(shapes):
40 return None
41
42 is_hwc = True
43 is_chw = True
44 for arr, shape in zip(inputs, shapes):
45 shape_hwc = (shape[0], shape[1], shape[2])
46 shape_chw = (shape[2], shape[0], shape[1])
47 is_hwc = is_hwc and _is_valid_shape(arr.shape, shape_hwc)
48 is_chw = is_chw and _is_valid_shape(arr.shape, shape_chw)
49
50 if not is_hwc and not is_chw:
51 return None
52 # If both `is_hwc` and `is_chw` are `True`, the memory format is assumed to be HWC.
53 return is_hwc
54
55
56def _find_matching_variant_idx_and_is_hwc(
57 model, inputs: List[np.ndarray]
58) -> Tuple[int, bool]:
59 variant_idx = None
60 is_hwc = None
61 for i in range(model.get_num_model_variants()):
62 is_hwc = _is_shape_hwc(
63 inputs, model.get_model_variant_handle(i).get_model_input_shape()
64 )
65 if is_hwc is not None:
66 variant_idx = i
67 break
68
69 if is_hwc is None:
70 raise ValueError("Input shape is invalid.")
71 assert variant_idx is not None
72 return variant_idx, is_hwc
73
74
75# shape에 맞게 numpy ndarray를 생성한다.
76def _build_outputs(
77 shapes: List[_Shape], is_hwc: bool, dtype: np.dtype
78) -> List[np.ndarray]:
79 outputs = []
80 for shape in shapes:
81 if is_hwc:
82 shape = (shape[0], shape[1], shape[2])
83 else:
84 shape = (shape[2], shape[0], shape[1])
85 outputs.append(np.empty(shape, dtype=dtype))
86 return outputs
87
88
89# output에 들어있는 numpy ndarray의 shape가 올바른지 검사한다.
90def _check_output_shapes(
91 outputs: List[np.ndarray], shapes: List[_Shape], is_hwc: bool, dtype: np.dtype
92) -> None:
93 if len(outputs) != len(shapes):
94 raise ValueError("The number of outputs is different.")
95
96 for output, shape in zip(outputs, shapes):
97 if output.dtype != dtype:
98 raise ValueError("Output dtype mismatch.")
99
100 if is_hwc:
101 shape = (shape[0], shape[1], shape[2])
102 else:
103 shape = (shape[2], shape[0], shape[1])
104 if output.shape != shape:
105 raise ValueError("Output shape mismatch.")
106
107
108class Model:
109 def __init__(self, path: str, model_config: Optional[ModelConfig] = None):
110 if model_config is None:
111 self._model = _cMaccel.Model(path)
112 else:
113 self._model = _cMaccel.Model(path, model_config._model_config)
114
115 # 기존 BufferInfo 대신에 ModelShape를 사용한다.
116 # Model {input,output} shape는 batch를 포함한 4D이다.
119
120 def launch(self, acc: Accelerator) -> None:
121 self._model.launch(acc._accelerator)
122 self._acc = acc
123
124 def dispose(self) -> None:
125 self._model.dispose()
126 self._acc = None
127
128 def is_target(self, core_id: CoreId) -> bool:
129 return self._model.is_target(core_id._core_id)
130
131 def get_core_mode(self) -> CoreMode:
132 return CoreMode(self._model.get_core_mode())
133
134 def get_target_cores(self) -> List[CoreId]:
135 return [CoreId.from_cpp(target) for target in self._model.target_cores]
136
137 # Deprecated
138 @property
139 def target_cores(self) -> List[CoreId]:
140 return [CoreId.from_cpp(target) for target in self._model.target_cores]
141
142 # 1. infer(in:List[numpy]) -> List[numpy] (float / int)
143 # 2. infer(in:numpy) -> List[numpy] (float / int)
144 # 3. infer(in:List[numpy], out:List[numpy]) (float / int)
145 # 4. infer(in:List[numpy], out:List[]) (float / int)
146 # 5. infer(in:numpy, out:List[numpy]) (float / int)
147 # 6. infer(in:numpy, out:List[]) (float / int)
148 def infer(
149 self,
150 inputs: Union[np.ndarray, List[np.ndarray]],
151 outputs: Optional[List[np.ndarray]] = None,
152 cache_size: int = 0,
153 ) -> Optional[List[np.ndarray]]:
154 if not isinstance(inputs, list):
155 inputs = [inputs]
156
157 variant_idx, is_hwc = _find_matching_variant_idx_and_is_hwc(self, inputs)
158 inputs = [np.ascontiguousarray(i) for i in inputs]
159
160 if outputs is None:
161 # No Output Parameter
162 infer_func = self._model.infer if is_hwc else self._model.infer_chw
163 return [np.asarray(o) for o in infer_func(inputs, cache_size)]
164
165 else:
166 if outputs:
167 _check_output_shapes(
168 outputs,
169 self.get_model_variant_handle(variant_idx).get_model_output_shape(),
170 is_hwc,
171 inputs[0].dtype,
172 )
173 for oi in range(len(outputs)):
174 outputs[oi] = np.ascontiguousarray(outputs[oi])
175 else:
176 outputs[:] = _build_outputs(
177 self.get_model_variant_handle(variant_idx).get_model_output_shape(),
178 is_hwc,
179 inputs[0].dtype,
180 )
181
182 if is_hwc:
183 self._model.infer(inputs, outputs, cache_size)
184 else:
185 self._model.infer_chw(inputs, outputs, cache_size)
186
187 def infer_to_float(
188 self,
189 inputs: Union[
190 np.ndarray,
191 List[np.ndarray],
192 ],
193 cache_size: int = 0,
194 ) -> List[np.ndarray]:
195 if not isinstance(inputs, list):
196 inputs = [inputs]
197
198 _, is_hwc = _find_matching_variant_idx_and_is_hwc(self, inputs)
199 inputs = [np.ascontiguousarray(i) for i in inputs]
200
201 if is_hwc:
202 outputs = self._model.infer_to_float(inputs, cache_size)
203 else:
204 outputs = self._model.infer_chw_to_float(inputs, cache_size)
205
206 return [np.asarray(o) for o in outputs]
207
208 # For backward compatibility.
209 infer_chw = infer
210 infer_chw_to_float = infer_to_float
211
212 def infer_buffer(
213 self,
214 inputs: List[Buffer],
215 outputs: List[Buffer],
216 shape: List[List[int]] = [],
217 cache_size: int = 0,
218 ) -> None:
219 self._model.infer_buffer(
220 [i._buffer for i in inputs], [o._buffer for o in outputs], shape, cache_size
221 )
222
223 def infer_speedrun(self) -> None:
224 self._model.infer_speedrun()
225
226 def infer_async(
227 self,
228 inputs: Union[np.ndarray, List[np.ndarray]],
229 ) -> Future:
230 if not isinstance(inputs, list):
231 inputs = [inputs]
232 _, is_hwc = _find_matching_variant_idx_and_is_hwc(self, inputs)
233 inputs = [np.ascontiguousarray(i) for i in inputs]
234 infer_async_func = (
235 self._model.infer_async if is_hwc else self._model.infer_async_chw
236 )
237 return Future.from_cpp(infer_async_func(inputs), inputs)
238
239 def reposition_inputs(
240 self,
241 inputs: List[np.ndarray],
242 input_bufs: List[Buffer],
243 seqlens: List[List[int]] = [],
244 ) -> None:
245 inputs = [np.ascontiguousarray(i) for i in inputs]
246 self._model.reposition_inputs(
247 inputs, [buf._buffer for buf in input_bufs], seqlens
248 )
249
250 def reposition_outputs(
251 self,
252 output_bufs: List[Buffer],
253 outputs: List[np.ndarray],
254 seqlens: List[List[int]] = [],
255 ) -> None:
256 if len(outputs) != len(self._output_shape):
257 outputs.clear()
258 for shape in self._output_shape:
259 outputs.append(np.empty(shape=shape, dtype=np.float32))
260 else:
261 for oi in range(len(outputs)):
262 outputs[oi] = np.ascontiguousarray(outputs[oi])
263 self._model.reposition_outputs(
264 [buf._buffer for buf in output_bufs], outputs, seqlens
265 )
266
267 def get_num_model_variants(self) -> int:
268 return self._model.get_num_model_variants()
269
270 def get_model_variant_handle(self, variant_idx) -> ModelVariantHandle:
271 return ModelVariantHandle.from_cpp(
272 self._model.get_model_variant_handle(variant_idx)
273 )
274
275 def get_model_input_shape(self) -> List[_Shape]:
276 return self._model.get_model_input_shape()
277
278 def get_model_output_shape(self) -> List[_Shape]:
279 return self._model.get_model_output_shape()
280
281 def get_input_scale(self) -> List[Scale]:
282 return [Scale.from_cpp(s) for s in self._model.get_input_scale()]
283
284 def get_output_scale(self) -> List[Scale]:
285 return [Scale.from_cpp(s) for s in self._model.get_output_scale()]
286
287 def get_input_buffer_info(self) -> List[BufferInfo]:
288 return [BufferInfo.from_cpp(bi) for bi in self._model.get_input_buffer_info()]
289
290 def get_output_buffer_info(self) -> List[BufferInfo]:
291 return [BufferInfo.from_cpp(bi) for bi in self._model.get_output_buffer_info()]
292
293 def acquire_input_buffer(self, seqlens: List[List[int]] = []) -> List[Buffer]:
294 return [Buffer(b) for b in self._model.acquire_input_buffer(seqlens)]
295
296 def acquire_output_buffer(self, seqlens: List[List[int]] = []) -> List[Buffer]:
297 return [Buffer(b) for b in self._model.acquire_output_buffer(seqlens)]
298
299 def release_buffer(self, buffer: List[Buffer]) -> None:
300 self._model.release_buffer([b._buffer for b in buffer])
301
302 def get_identifier(self) -> int:
303 return self._model.get_identifier()
304
305 def get_model_path(self) -> str:
306 return self._model.get_model_path()
307
308 def get_cache_infos(self) -> List[CacheInfo]:
309 return [CacheInfo.from_cpp(c) for c in self._model.get_cache_infos()]
310
311 def get_schedule_policy(self) -> SchedulePolicy:
312 return SchedulePolicy(self._model.get_schedule_policy())
313
314 def get_latency_set_policy(self) -> LatencySetPolicy:
315 return LatencySetPolicy(self._model.get_latency_set_policy())
316
317 def get_maintenance_policy(self) -> MaintenancePolicy:
318 return MaintenancePolicy(self._model.get_maintenance_policy())
319
320 def get_latency_consumed(self) -> int:
321 return self._model.get_latency_consumed()
322
323 def get_latency_finished(self) -> int:
324 return self._model.get_latency_finished()
325
326 def reset_cache_memory(self) -> None:
327 self._model.reset_cache_memory()
328
329 def dump_cache_memory(self) -> List[bytes]:
330 bufs = self._model.dump_cache_memory()
331 return [np.asarray(buf, np.int8).tobytes() for buf in bufs]
332
333 def load_cache_memory(self, bufs: List[bytes]) -> None:
334 self._model.load_cache_memory(
335 [np.frombuffer(buf, dtype=np.int8) for buf in bufs]
336 )
337
338 def dump_cache_memory_to(self, cache_dir: str) -> None:
339 self._model.dump_cache_memory(cache_dir)
340
341 def load_cache_memory_from(self, cache_dir: str) -> None:
342 self._model.load_cache_memory(cache_dir)
343
344 def filter_cache_tail(
345 self, cache_size: int, tail_size: int, mask: List[bool]
346 ) -> int:
347 return self._model.filter_cache_tail(cache_size, tail_size, mask)
348
349 def move_cache_tail(self, num_head: int, num_tail: int, cache_size: int) -> int:
350 return self._model.move_cache_tail(num_head, num_tail, cache_size)
351
352
353def load(path: str, model_config: Optional[ModelConfig] = None) -> Model:
354 acc = Accelerator()
355 model = Model(path, model_config)
356 model.launch(acc)
357 return model
358
359
List[_Shape] _input_shape
Definition model.py:117
List[_Shape] get_model_input_shape(self)
Definition model.py:275
ModelVariantHandle get_model_variant_handle(self, variant_idx)
Definition model.py:270
List[_Shape] _output_shape
Definition model.py:118
List[_Shape] get_model_output_shape(self)
Definition model.py:278