def update_model(cls, model: ModelBO, force_insert=False): """Update a model to ModelDB and GridFS. The function will check the existence of the provided model. It will invoke the update. Note that this function will have not effect for static profiling result and dynamic profiling result. Please see `register_static_profiling_result` and ` Args: model (ModelBO): model business object to be updated. The model must exist in the ModelDB based on its `id`. Otherwise, you should set `force_insert` to be `True`. force_insert (bool: `True`, optional): Force insert flag for ModelDB. The model will force to insert regardless its existence. Return: True for successfully update, False otherwise. Raises: ValueError: If `model.id` does not exist in ModelDB, and `force_insert` is not set. """ # check for the existence of Model if cls.__model_DAO.exists_by_id(model.id): model_po_new = model.to_model_do() model_po = cls.__model_DAO.get_model_by_id(model.id) # if weight changes, save all without patch if model_po_new.weight is not None and model_po_new.weight != model_po.weight: return bool(cls.__model_DAO.save_model(model_po_new)) # build arguments valid_keys = [ 'name', 'framework', 'engine', 'version', 'dataset', 'accuracy', 'weight', 'task', 'inputs', 'outputs', 'status' ] kwargs = dict() for valid_key in valid_keys: new_value = getattr(model_po_new, valid_key) if new_value is not None and new_value != getattr( model_po, valid_key): kwargs[valid_key] = new_value # if kwargs is empty, not update if len(kwargs) == 0: return False return bool(cls.__model_DAO.update_model(model.id, **kwargs)) # return bool(cls.__model_DAO.update_model(model_po_new)) else: # if `force_insert` is set if force_insert: model_po = model.to_model_do() return bool(cls.__model_DAO.save_model(model_po)) else: raise ValueError( 'Model ID {} does not exist. You may change the ID or set `force_insert=True` ' 'when call.'.format(model.id))
def post_model(cls, model: ModelBO): """Register a model into ModelDB and GridFS. `model.id` should be set as `None`, otherwise, the function will raise a `ValueError`. Args: model (ModelBO): model business object to be registered Return: bool: True if successful, False otherwise Raises: BadRequestValueException: If `model.id` is not None. ServiceException: If model has exists with the same primary keys (name, framework, engine and version). """ # check model id if model.id is not None: raise BadRequestValueException( 'field `id` is expected `None`, but got {}. You should use `update_model` for a existing ' 'model BO'.format(model.id)) model_po = model.to_model_do() if cls.__model_DAO.exists_by_primary_keys(name=model_po.name, framework=model_po.framework, engine=model_po.engine, version=model_po.version): raise ServiceException( f'Model business object with primary keys name={model_po.name}, framework={model_po.framework}, ' f'engine={model_po.engine}, version={model_po.version} has exists.' ) return bool(cls.__model_DAO.save_model(model_po))
def get_model_by_id(cls, id_: str): """Get model given model ID. Args: id_ (str): model ID, should be a valid BSON Object ID Return: A model BO if found. Raises: DoesNotExistException: If the model with given ID does not found. """ if not cls.__model_DAO.exists_by_id(ObjectId(id_)): raise DoesNotExistException(f'Model id={id_} does not exists.') return ModelBO.from_model_do(cls.__model_DAO.get_model_by_id(ObjectId(id_)))
def test_register_model(): model = ModelBO('ResNet50', framework=Framework.PYTORCH, engine=Engine.TRT, version=ModelVersion(1), dataset='ImageNet', acc=0.8, task='image classification', inputs=[ IOShape([-1, 3, 224, 224], dtype=float, format=ModelInputFormat.FORMAT_NCHW) ], outputs=[IOShape([-1, 1000], dtype=int)], weight=Weight(bytes([123]))) assert ModelService.post_model(model)
def test_register_model(): model = ModelBO('ResNet50', framework=Framework.PYTORCH, engine=Engine.TRT, version=ModelVersion(1), dataset='ImageNet', metric={Metric.ACC: 0.80}, task=Task.IMAGE_CLASSIFICATION, inputs=[ IOShape([-1, 3, 224, 224], dtype=float, format=ModelInputFormat.FORMAT_NCHW) ], outputs=[IOShape([-1, 1000], dtype=int)], weight=Weight(bytes([123]))) assert ModelService.post_model(model)
def register_model(origin_model, dataset: str, metric: Dict[Metric, float], task: Task, inputs: List[IOShape], outputs: List[IOShape], model_input: Optional[List] = None, architecture: str = None, framework: Framework = None, engine: Engine = None, version: ModelVersion = None, parent_model_id: Optional[str] = None, convert: bool = True, profile: bool = True, model_status: List[ModelStatus] = None): """Upload a model to ModelDB. This function will upload the given model into the database with some variation. It may optionally generate a branch of models (i.e. model family) with different optimization techniques. Besides, a benchmark will be scheduled for each generated model, in order to gain profiling results for model selection strategies. In the `no_generate` model(i.e. `no_generate` flag is set to be `True`), `architecture`, `framework`, `engine` and `version` could be None. If any of the above arguments is `None`, all of them will be auto induced from the origin_model path. An `ValueError` will be raised if the mata info cannot be induced. TODO: This function has a super comprehensive logic, need to be simplified. Arguments: origin_model: The uploaded model without optimization. When `no_generate` flag is set, this parameter should be a str indicating model file path. architecture (str): Model architecture name. Default to None. framework (Framework): Framework name. Default to None. version (ModelVersion): Model version. Default to None. dataset (str): Model testing dataset. metric (Dict[Metric,float]): Scoring metric and its corresponding score used for model evaluation task (Task): Model task type. inputs (Iterable[IOShape]): Model input tensors. outputs (Iterable[IOShape]): Model output tensors. model_input: specify sample model input data TODO: specify more model conversion related params engine (Engine): Model optimization engine. Default to `Engine.NONE`. parent_model_id (Optional[str]): the parent model id of current model if this model is derived from a pre-existing one model_status (List[ModelStatus]): Indicate the status of current model in its lifecycle convert (bool): Flag for generation of model family. When set, `origin_model` should be a path to model saving file. Default to `True`. profile (bool): Flag for profiling uploaded (including converted) models. Default to `False`. """ from modelci.controller import job_executor from modelci.controller.executor import Job model_dir_list = list() # type and existence check if isinstance(origin_model, str): model_dir = Path(origin_model).absolute() assert model_dir.exists( ), f'model weight does not exist at {origin_model}' if all([architecture, task, framework, engine, version]): # from explicit architecture, framework, engine and version ext = model_dir.suffix path = generate_path(architecture, task, framework, engine, version).with_suffix(ext) # if already in the destination folder if path == model_dir: pass # create destination folder else: if ext: path.parent.mkdir(parents=True, exist_ok=True) else: path.mkdir(parents=True, exist_ok=True) # copy to cached folder subprocess.call(['cp', model_dir, path]) else: # from implicit extracted from path, check validity of the path later at registration path = model_dir model_dir_list.append(path) elif framework == Framework.PYTORCH and engine in [ Engine.PYTORCH, Engine.NONE ]: # save original pytorch model pytorch_dir = generate_path( task=task, model_name=architecture, framework=framework, engine=engine, version=str(version), ) pytorch_dir.parent.mkdir(parents=True, exist_ok=True) save_path_with_ext = pytorch_dir.with_suffix('.pth') torch.save(origin_model, str(save_path_with_ext)) model_dir_list.append(pytorch_dir.with_suffix('.pth')) if convert: # TODO: generate from path name # generate model variant model_dir_list.extend( _generate_model_family(origin_model, architecture, task, framework, filename=str(version), inputs=inputs, outputs=outputs, model_input=model_input)) # register for model_dir in model_dir_list: parse_result = parse_path(model_dir) architecture = parse_result['architecture'] task = parse_result['task'] framework = parse_result['framework'] engine = parse_result['engine'] version = parse_result['version'] filename = parse_result['filename'] if model_status is not None: model_bo_status = model_status elif engine == Engine.PYTORCH: model_bo_status = [ModelStatus.PUBLISHED] else: model_bo_status = [ModelStatus.CONVERTED] with open(str(model_dir), 'rb') as f: model = ModelBO(name=architecture, task=task, framework=framework, engine=engine, version=version, dataset=dataset, metric=metric, parent_model_id=parent_model_id, inputs=inputs, outputs=outputs, model_status=model_bo_status, weight=Weight(f, filename=filename)) ModelService.post_model(model) # TODO refresh model = ModelService.get_models(name=architecture, task=task, framework=framework, engine=engine, version=version)[0] if model.engine == Engine.PYTORCH or model.engine == Engine.TFS: parent_model_id = model.id # profile registered model if profile and engine != Engine.PYTORCH: file = tf.keras.utils.get_file( "grace_hopper.jpg", "https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg" ) test_img_bytes = cv2.imread(file) kwargs = { 'repeat_data': test_img_bytes, 'batch_size': 32, 'batch_num': 100, 'asynchronous': False, 'model_info': model, } new_status = [ item for item in model.model_status if item is not (ModelStatus.CONVERTED or ModelStatus.PUBLISHED) ] new_status.append(ModelStatus.PROFILING) model.model_status = new_status ModelService.update_model(model) if engine == Engine.TORCHSCRIPT: client = CVTorchClient(**kwargs) elif engine == Engine.TFS: client = CVTFSClient(**kwargs) elif engine == Engine.ONNX: client = CVONNXClient(**kwargs) elif engine == Engine.TRT: client = CVTRTClient(**kwargs) else: raise ValueError(f'No such serving engine: {engine}') job_cuda = Job(client=client, device='cuda:0', model_info=model) # job_cpu = Job(client=client, device='cpu', model_info=model) job_executor.submit(job_cuda)
def register_model( origin_model, dataset: str, acc: float, task: str, inputs: List[IOShape], outputs: List[IOShape], architecture: str = None, framework: Framework = None, engine: Engine = None, version: ModelVersion = None, convert=True, profile=True, ): """Upload a model to ModelDB. This function will upload the given model into the database with some variation. It may optionally generate a branch of models (i.e. model family) with different optimization techniques. Besides, a benchmark will be scheduled for each generated model, in order to gain profiling results for model selection strategies. In the `no_generate` model(i.e. `no_generate` flag is set to be `True`), `architecture`, `framework`, `engine` and `version` could be None. If any of the above arguments is `None`, all of them will be auto induced from the origin_model path. An `ValueError` will be raised if the mata info cannot be induced. Arguments: origin_model: The uploaded model without optimization. When `no_generate` flag is set, this parameter should be a str indicating model file path. architecture (str): Model architecture name. Default to None. framework (Framework): Framework name. Default to None. version (ModelVersion): Model version. Default to None. dataset (str): Model testing dataset. acc (float): Model accuracy on the testing dataset. task (str): Model task type. inputs (Iterable[IOShape]): Model input tensors. outputs (Iterable[IOShape]): Model output tensors. engine (Engine): Model optimization engine. Default to `Engine.NONE`. convert (bool): Flag for generation of model family. When set, `origin_model` should be a path to model saving file. Default to `True`. profile (bool): Flag for profiling uploaded (including converted) models. Default to `False`. """ from modelci.controller import job_executor from modelci.controller.executor import Job model_dir_list = list() if not convert: # type and existence check assert isinstance(origin_model, str) model_dir = Path(origin_model).absolute() assert model_dir.exists( ), f'model weight does not exist at {origin_model}' if all([ architecture, framework, engine, version ]): # from explicit architecture, framework, engine and version ext = model_dir.suffix path = generate_path(architecture, framework, engine, version).with_suffix(ext) # if already in the destination folder if path == model_dir: pass # create destination folder else: if ext: path.parent.mkdir(parents=True, exist_ok=True) else: path.mkdir(parents=True, exist_ok=True) # copy to cached folder subprocess.call(['cp', model_dir, path]) else: # from implicit extracted from path, check validity of the path later at registration path = model_dir model_dir_list.append(path) else: # TODO: generate from path name # generate model variant model_dir_list.extend( _generate_model_family(origin_model, architecture, framework, filename=str(version), inputs=inputs, outputs=outputs)) # register for model_dir in model_dir_list: parse_result = parse_path(model_dir) architecture = parse_result['architecture'] framework = parse_result['framework'] engine = parse_result['engine'] version = parse_result['version'] filename = parse_result['filename'] with open(str(model_dir), 'rb') as f: model = ModelBO(name=architecture, framework=framework, engine=engine, version=version, dataset=dataset, acc=acc, task=task, inputs=inputs, outputs=outputs, weight=Weight(f, filename=filename)) ModelService.post_model(model) # TODO refresh model = ModelService.get_models(name=architecture, framework=framework, engine=engine, version=version)[0] # profile registered model if profile: file = tf.keras.utils.get_file( "grace_hopper.jpg", "https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg" ) test_img_bytes = cv2.imread(file) kwargs = { 'repeat_data': test_img_bytes, 'batch_size': 32, 'batch_num': 100, 'asynchronous': False, 'model_info': model, } if engine == Engine.TORCHSCRIPT: client = CVTorchClient(**kwargs) elif engine == Engine.TFS: client = CVTFSClient(**kwargs) elif engine == Engine.ONNX: client = CVONNXClient(**kwargs) elif engine == Engine.TRT: client = CVTRTClient(**kwargs) else: raise ValueError(f'No such serving engine: {engine}') job_cuda = Job(client=client, device='cuda:0', model_info=model) # job_cpu = Job(client=client, device='cpu', model_info=model) job_executor.submit(job_cuda)