예제 #1
0
    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))
예제 #2
0
    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))
예제 #3
0
파일: service.py 프로젝트: wx-b/ML-Model-CI
    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_)))
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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)